mirror of
https://github.com/pre-commit/pre-commit-hooks.git
synced 2026-04-16 00:20:13 +00:00
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
This commit is contained in:
parent
72ad6dc953
commit
f4cd1ba0d6
813 changed files with 66015 additions and 58839 deletions
|
|
@ -1,2 +1,3 @@
|
|||
"""Index interaction code
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
The main purpose of this module is to expose LinkCollector.collect_sources().
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import cgi
|
||||
import collections
|
||||
|
|
@ -14,23 +15,17 @@ import urllib.request
|
|||
import xml.etree.ElementTree
|
||||
from html.parser import HTMLParser
|
||||
from optparse import Values
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
MutableMapping,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
|
||||
from pip._vendor import html5lib, requests
|
||||
from pip._vendor.requests import Response
|
||||
from pip._vendor.requests.exceptions import RetryError, SSLError
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import MutableMapping
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from pip._internal.exceptions import NetworkConnectionError
|
||||
from pip._internal.models.link import Link
|
||||
|
|
@ -38,10 +33,18 @@ 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.filetypes import is_archive_file
|
||||
from pip._internal.utils.misc import pairwise, redact_auth_from_url
|
||||
from pip._internal.utils.misc import pairwise
|
||||
from pip._internal.utils.misc import redact_auth_from_url
|
||||
from pip._internal.vcs import vcs
|
||||
from pip._vendor import html5lib
|
||||
from pip._vendor import requests
|
||||
from pip._vendor.requests import Response
|
||||
from pip._vendor.requests.exceptions import RetryError
|
||||
from pip._vendor.requests.exceptions import SSLError
|
||||
|
||||
from .sources import CandidatesFromPage, LinkSource, build_source
|
||||
from .sources import build_source
|
||||
from .sources import CandidatesFromPage
|
||||
from .sources import LinkSource
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
|
|
@ -54,13 +57,13 @@ HTMLElement = xml.etree.ElementTree.Element
|
|||
ResponseHeaders = MutableMapping[str, str]
|
||||
|
||||
|
||||
def _match_vcs_scheme(url: str) -> Optional[str]:
|
||||
def _match_vcs_scheme(url: str) -> str | None:
|
||||
"""Look for VCS schemes in the URL.
|
||||
|
||||
Returns the matched VCS scheme, or None if there's no match.
|
||||
"""
|
||||
for scheme in vcs.schemes:
|
||||
if url.lower().startswith(scheme) and url[len(scheme)] in "+:":
|
||||
if url.lower().startswith(scheme) and url[len(scheme)] in '+:':
|
||||
return scheme
|
||||
return None
|
||||
|
||||
|
|
@ -77,8 +80,8 @@ def _ensure_html_header(response: Response) -> None:
|
|||
|
||||
Raises `_NotHTML` if the content type is not text/html.
|
||||
"""
|
||||
content_type = response.headers.get("Content-Type", "")
|
||||
if not content_type.lower().startswith("text/html"):
|
||||
content_type = response.headers.get('Content-Type', '')
|
||||
if not content_type.lower().startswith('text/html'):
|
||||
raise _NotHTML(content_type, response.request.method)
|
||||
|
||||
|
||||
|
|
@ -93,7 +96,7 @@ def _ensure_html_response(url: str, session: PipSession) -> None:
|
|||
`_NotHTML` if the content type is not text/html.
|
||||
"""
|
||||
scheme, netloc, path, query, fragment = urllib.parse.urlsplit(url)
|
||||
if scheme not in {"http", "https"}:
|
||||
if scheme not in {'http', 'https'}:
|
||||
raise _NotHTTP()
|
||||
|
||||
resp = session.head(url, allow_redirects=True)
|
||||
|
|
@ -118,12 +121,12 @@ def _get_html_response(url: str, session: PipSession) -> Response:
|
|||
if is_archive_file(Link(url).filename):
|
||||
_ensure_html_response(url, session=session)
|
||||
|
||||
logger.debug("Getting page %s", redact_auth_from_url(url))
|
||||
logger.debug('Getting page %s', redact_auth_from_url(url))
|
||||
|
||||
resp = session.get(
|
||||
url,
|
||||
headers={
|
||||
"Accept": "text/html",
|
||||
'Accept': 'text/html',
|
||||
# We don't want to blindly returned cached data for
|
||||
# /simple/, because authors generally expecting that
|
||||
# twine upload && pip install will function, but if
|
||||
|
|
@ -137,7 +140,7 @@ def _get_html_response(url: str, session: PipSession) -> Response:
|
|||
# trip for the conditional GET now instead of only
|
||||
# once per 10 minutes.
|
||||
# For more information, please see pypa/pip#5670.
|
||||
"Cache-Control": "max-age=0",
|
||||
'Cache-Control': 'max-age=0',
|
||||
},
|
||||
)
|
||||
raise_for_status(resp)
|
||||
|
|
@ -152,12 +155,12 @@ def _get_html_response(url: str, session: PipSession) -> Response:
|
|||
return resp
|
||||
|
||||
|
||||
def _get_encoding_from_headers(headers: ResponseHeaders) -> Optional[str]:
|
||||
def _get_encoding_from_headers(headers: ResponseHeaders) -> str | None:
|
||||
"""Determine if we have any encoding information in our headers."""
|
||||
if headers and "Content-Type" in headers:
|
||||
content_type, params = cgi.parse_header(headers["Content-Type"])
|
||||
if "charset" in params:
|
||||
return params["charset"]
|
||||
if headers and 'Content-Type' in headers:
|
||||
content_type, params = cgi.parse_header(headers['Content-Type'])
|
||||
if 'charset' in params:
|
||||
return params['charset']
|
||||
return None
|
||||
|
||||
|
||||
|
|
@ -175,8 +178,8 @@ def _determine_base_url(document: HTMLElement, page_url: str) -> str:
|
|||
|
||||
TODO: Remove when `html5lib` is dropped.
|
||||
"""
|
||||
for base in document.findall(".//base"):
|
||||
href = base.get("href")
|
||||
for base in document.findall('.//base'):
|
||||
href = base.get('href')
|
||||
if href is not None:
|
||||
return href
|
||||
return page_url
|
||||
|
|
@ -204,7 +207,7 @@ def _clean_file_url_path(part: str) -> str:
|
|||
|
||||
|
||||
# percent-encoded: /
|
||||
_reserved_chars_re = re.compile("(@|%2F)", re.IGNORECASE)
|
||||
_reserved_chars_re = re.compile('(@|%2F)', re.IGNORECASE)
|
||||
|
||||
|
||||
def _clean_url_path(path: str, is_local_path: bool) -> str:
|
||||
|
|
@ -221,12 +224,12 @@ def _clean_url_path(path: str, is_local_path: bool) -> str:
|
|||
parts = _reserved_chars_re.split(path)
|
||||
|
||||
cleaned_parts = []
|
||||
for to_clean, reserved in pairwise(itertools.chain(parts, [""])):
|
||||
for to_clean, reserved in pairwise(itertools.chain(parts, [''])):
|
||||
cleaned_parts.append(clean_func(to_clean))
|
||||
# Normalize %xx escapes (e.g. %2f -> %2F)
|
||||
cleaned_parts.append(reserved.upper())
|
||||
|
||||
return "".join(cleaned_parts)
|
||||
return ''.join(cleaned_parts)
|
||||
|
||||
|
||||
def _clean_link(url: str) -> str:
|
||||
|
|
@ -245,20 +248,20 @@ def _clean_link(url: str) -> str:
|
|||
|
||||
|
||||
def _create_link_from_element(
|
||||
element_attribs: Dict[str, Optional[str]],
|
||||
element_attribs: dict[str, str | None],
|
||||
page_url: str,
|
||||
base_url: str,
|
||||
) -> Optional[Link]:
|
||||
) -> Link | None:
|
||||
"""
|
||||
Convert an anchor element's attributes in a simple repository page to a Link.
|
||||
"""
|
||||
href = element_attribs.get("href")
|
||||
href = element_attribs.get('href')
|
||||
if not href:
|
||||
return None
|
||||
|
||||
url = _clean_link(urllib.parse.urljoin(base_url, href))
|
||||
pyrequire = element_attribs.get("data-requires-python")
|
||||
yanked_reason = element_attribs.get("data-yanked")
|
||||
pyrequire = element_attribs.get('data-requires-python')
|
||||
yanked_reason = element_attribs.get('data-yanked')
|
||||
|
||||
link = Link(
|
||||
url,
|
||||
|
|
@ -271,7 +274,7 @@ def _create_link_from_element(
|
|||
|
||||
|
||||
class CacheablePageContent:
|
||||
def __init__(self, page: "HTMLPage") -> None:
|
||||
def __init__(self, page: HTMLPage) -> None:
|
||||
assert page.cache_link_parsing
|
||||
self.page = page
|
||||
|
||||
|
|
@ -284,7 +287,7 @@ class CacheablePageContent:
|
|||
|
||||
class ParseLinks(Protocol):
|
||||
def __call__(
|
||||
self, page: "HTMLPage", use_deprecated_html5lib: bool
|
||||
self, page: HTMLPage, use_deprecated_html5lib: bool,
|
||||
) -> Iterable[Link]:
|
||||
...
|
||||
|
||||
|
|
@ -298,12 +301,12 @@ def with_cached_html_pages(fn: ParseLinks) -> ParseLinks:
|
|||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def wrapper(
|
||||
cacheable_page: CacheablePageContent, use_deprecated_html5lib: bool
|
||||
) -> List[Link]:
|
||||
cacheable_page: CacheablePageContent, use_deprecated_html5lib: bool,
|
||||
) -> list[Link]:
|
||||
return list(fn(cacheable_page.page, use_deprecated_html5lib))
|
||||
|
||||
@functools.wraps(fn)
|
||||
def wrapper_wrapper(page: "HTMLPage", use_deprecated_html5lib: bool) -> List[Link]:
|
||||
def wrapper_wrapper(page: HTMLPage, use_deprecated_html5lib: bool) -> list[Link]:
|
||||
if page.cache_link_parsing:
|
||||
return wrapper(CacheablePageContent(page), use_deprecated_html5lib)
|
||||
return list(fn(page, use_deprecated_html5lib))
|
||||
|
|
@ -311,7 +314,7 @@ def with_cached_html_pages(fn: ParseLinks) -> ParseLinks:
|
|||
return wrapper_wrapper
|
||||
|
||||
|
||||
def _parse_links_html5lib(page: "HTMLPage") -> Iterable[Link]:
|
||||
def _parse_links_html5lib(page: HTMLPage) -> Iterable[Link]:
|
||||
"""
|
||||
Parse an HTML document, and yield its anchor elements as Link objects.
|
||||
|
||||
|
|
@ -325,7 +328,7 @@ def _parse_links_html5lib(page: "HTMLPage") -> Iterable[Link]:
|
|||
|
||||
url = page.url
|
||||
base_url = _determine_base_url(document, url)
|
||||
for anchor in document.findall(".//a"):
|
||||
for anchor in document.findall('.//a'):
|
||||
link = _create_link_from_element(
|
||||
anchor.attrib,
|
||||
page_url=url,
|
||||
|
|
@ -337,7 +340,7 @@ def _parse_links_html5lib(page: "HTMLPage") -> Iterable[Link]:
|
|||
|
||||
|
||||
@with_cached_html_pages
|
||||
def parse_links(page: "HTMLPage", use_deprecated_html5lib: bool) -> Iterable[Link]:
|
||||
def parse_links(page: HTMLPage, use_deprecated_html5lib: bool) -> Iterable[Link]:
|
||||
"""
|
||||
Parse an HTML document, and yield its anchor elements as Link objects.
|
||||
"""
|
||||
|
|
@ -347,7 +350,7 @@ def parse_links(page: "HTMLPage", use_deprecated_html5lib: bool) -> Iterable[Lin
|
|||
return
|
||||
|
||||
parser = HTMLLinkParser(page.url)
|
||||
encoding = page.encoding or "utf-8"
|
||||
encoding = page.encoding or 'utf-8'
|
||||
parser.feed(page.content.decode(encoding))
|
||||
|
||||
url = page.url
|
||||
|
|
@ -369,7 +372,7 @@ class HTMLPage:
|
|||
def __init__(
|
||||
self,
|
||||
content: bytes,
|
||||
encoding: Optional[str],
|
||||
encoding: str | None,
|
||||
url: str,
|
||||
cache_link_parsing: bool = True,
|
||||
) -> None:
|
||||
|
|
@ -399,32 +402,32 @@ class HTMLLinkParser(HTMLParser):
|
|||
super().__init__(convert_charrefs=True)
|
||||
|
||||
self.url: str = url
|
||||
self.base_url: Optional[str] = None
|
||||
self.anchors: List[Dict[str, Optional[str]]] = []
|
||||
self.base_url: str | None = None
|
||||
self.anchors: list[dict[str, str | None]] = []
|
||||
|
||||
def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None:
|
||||
if tag == "base" and self.base_url is None:
|
||||
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
|
||||
if tag == 'base' and self.base_url is None:
|
||||
href = self.get_href(attrs)
|
||||
if href is not None:
|
||||
self.base_url = href
|
||||
elif tag == "a":
|
||||
elif tag == 'a':
|
||||
self.anchors.append(dict(attrs))
|
||||
|
||||
def get_href(self, attrs: List[Tuple[str, Optional[str]]]) -> Optional[str]:
|
||||
def get_href(self, attrs: list[tuple[str, str | None]]) -> str | None:
|
||||
for name, value in attrs:
|
||||
if name == "href":
|
||||
if name == 'href':
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def _handle_get_page_fail(
|
||||
link: Link,
|
||||
reason: Union[str, Exception],
|
||||
meth: Optional[Callable[..., None]] = None,
|
||||
reason: str | Exception,
|
||||
meth: Callable[..., None] | None = None,
|
||||
) -> None:
|
||||
if meth is None:
|
||||
meth = logger.debug
|
||||
meth("Could not fetch URL %s: %s - skipping", link, reason)
|
||||
meth('Could not fetch URL %s: %s - skipping', link, reason)
|
||||
|
||||
|
||||
def _make_html_page(response: Response, cache_link_parsing: bool = True) -> HTMLPage:
|
||||
|
|
@ -438,20 +441,20 @@ def _make_html_page(response: Response, cache_link_parsing: bool = True) -> HTML
|
|||
|
||||
|
||||
def _get_html_page(
|
||||
link: Link, session: Optional[PipSession] = None
|
||||
) -> Optional["HTMLPage"]:
|
||||
link: Link, session: PipSession | None = None,
|
||||
) -> HTMLPage | None:
|
||||
if session is None:
|
||||
raise TypeError(
|
||||
"_get_html_page() missing 1 required keyword argument: 'session'"
|
||||
"_get_html_page() missing 1 required keyword argument: 'session'",
|
||||
)
|
||||
|
||||
url = link.url.split("#", 1)[0]
|
||||
url = link.url.split('#', 1)[0]
|
||||
|
||||
# Check for VCS schemes that do not support lookup as web pages.
|
||||
vcs_scheme = _match_vcs_scheme(url)
|
||||
if vcs_scheme:
|
||||
logger.warning(
|
||||
"Cannot look at %s URL %s because it does not support lookup as web pages.",
|
||||
'Cannot look at %s URL %s because it does not support lookup as web pages.',
|
||||
vcs_scheme,
|
||||
link,
|
||||
)
|
||||
|
|
@ -459,26 +462,26 @@ def _get_html_page(
|
|||
|
||||
# Tack index.html onto file:// URLs that point to directories
|
||||
scheme, _, path, _, _, _ = urllib.parse.urlparse(url)
|
||||
if scheme == "file" and os.path.isdir(urllib.request.url2pathname(path)):
|
||||
if scheme == 'file' and os.path.isdir(urllib.request.url2pathname(path)):
|
||||
# add trailing slash if not present so urljoin doesn't trim
|
||||
# final segment
|
||||
if not url.endswith("/"):
|
||||
url += "/"
|
||||
url = urllib.parse.urljoin(url, "index.html")
|
||||
logger.debug(" file: URL is directory, getting %s", url)
|
||||
if not url.endswith('/'):
|
||||
url += '/'
|
||||
url = urllib.parse.urljoin(url, 'index.html')
|
||||
logger.debug(' file: URL is directory, getting %s', url)
|
||||
|
||||
try:
|
||||
resp = _get_html_response(url, session=session)
|
||||
except _NotHTTP:
|
||||
logger.warning(
|
||||
"Skipping page %s because it looks like an archive, and cannot "
|
||||
"be checked by a HTTP HEAD request.",
|
||||
'Skipping page %s because it looks like an archive, and cannot '
|
||||
'be checked by a HTTP HEAD request.',
|
||||
link,
|
||||
)
|
||||
except _NotHTML as exc:
|
||||
logger.warning(
|
||||
"Skipping page %s because the %s request got Content-Type: %s."
|
||||
"The only supported Content-Type is text/html",
|
||||
'Skipping page %s because the %s request got Content-Type: %s.'
|
||||
'The only supported Content-Type is text/html',
|
||||
link,
|
||||
exc.request_desc,
|
||||
exc.content_type,
|
||||
|
|
@ -488,21 +491,21 @@ def _get_html_page(
|
|||
except RetryError as exc:
|
||||
_handle_get_page_fail(link, exc)
|
||||
except SSLError as exc:
|
||||
reason = "There was a problem confirming the ssl certificate: "
|
||||
reason = 'There was a problem confirming the ssl certificate: '
|
||||
reason += str(exc)
|
||||
_handle_get_page_fail(link, reason, meth=logger.info)
|
||||
except requests.ConnectionError as exc:
|
||||
_handle_get_page_fail(link, f"connection error: {exc}")
|
||||
_handle_get_page_fail(link, f'connection error: {exc}')
|
||||
except requests.Timeout:
|
||||
_handle_get_page_fail(link, "timed out")
|
||||
_handle_get_page_fail(link, 'timed out')
|
||||
else:
|
||||
return _make_html_page(resp, cache_link_parsing=link.cache_link_parsing)
|
||||
return None
|
||||
|
||||
|
||||
class CollectedSources(NamedTuple):
|
||||
find_links: Sequence[Optional[LinkSource]]
|
||||
index_urls: Sequence[Optional[LinkSource]]
|
||||
find_links: Sequence[LinkSource | None]
|
||||
index_urls: Sequence[LinkSource | None]
|
||||
|
||||
|
||||
class LinkCollector:
|
||||
|
|
@ -528,7 +531,7 @@ class LinkCollector:
|
|||
session: PipSession,
|
||||
options: Values,
|
||||
suppress_no_index: bool = False,
|
||||
) -> "LinkCollector":
|
||||
) -> LinkCollector:
|
||||
"""
|
||||
:param session: The Session to use to make requests.
|
||||
:param suppress_no_index: Whether to ignore the --no-index option
|
||||
|
|
@ -537,8 +540,8 @@ class LinkCollector:
|
|||
index_urls = [options.index_url] + options.extra_index_urls
|
||||
if options.no_index and not suppress_no_index:
|
||||
logger.debug(
|
||||
"Ignoring indexes: %s",
|
||||
",".join(redact_auth_from_url(url) for url in index_urls),
|
||||
'Ignoring indexes: %s',
|
||||
','.join(redact_auth_from_url(url) for url in index_urls),
|
||||
)
|
||||
index_urls = []
|
||||
|
||||
|
|
@ -556,10 +559,10 @@ class LinkCollector:
|
|||
return link_collector
|
||||
|
||||
@property
|
||||
def find_links(self) -> List[str]:
|
||||
def find_links(self) -> list[str]:
|
||||
return self.search_scope.find_links
|
||||
|
||||
def fetch_page(self, location: Link) -> Optional[HTMLPage]:
|
||||
def fetch_page(self, location: Link) -> HTMLPage | None:
|
||||
"""
|
||||
Fetch an HTML page containing package links.
|
||||
"""
|
||||
|
|
@ -594,15 +597,15 @@ class LinkCollector:
|
|||
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
lines = [
|
||||
f"* {s.link}"
|
||||
f'* {s.link}'
|
||||
for s in itertools.chain(find_links_sources, index_url_sources)
|
||||
if s is not None and s.link is not None
|
||||
]
|
||||
lines = [
|
||||
f"{len(lines)} location(s) to search "
|
||||
f"for versions of {project_name}:"
|
||||
f'{len(lines)} location(s) to search '
|
||||
f'for versions of {project_name}:',
|
||||
] + lines
|
||||
logger.debug("\n".join(lines))
|
||||
logger.debug('\n'.join(lines))
|
||||
|
||||
return CollectedSources(
|
||||
find_links=list(find_links_sources),
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
"""Routines related to PyPI, indexes"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
import logging
|
||||
import re
|
||||
from typing import FrozenSet, Iterable, List, Optional, Set, Tuple, Union
|
||||
from typing import FrozenSet
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import 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 parse as parse_version
|
||||
|
||||
from pip._internal.exceptions import (
|
||||
BestVersionAlreadyInstalled,
|
||||
DistributionNotFound,
|
||||
InvalidWheelFilename,
|
||||
UnsupportedWheel,
|
||||
)
|
||||
from pip._internal.index.collector import LinkCollector, parse_links
|
||||
from pip._internal.exceptions import BestVersionAlreadyInstalled
|
||||
from pip._internal.exceptions import DistributionNotFound
|
||||
from pip._internal.exceptions import InvalidWheelFilename
|
||||
from pip._internal.exceptions import UnsupportedWheel
|
||||
from pip._internal.index.collector import LinkCollector
|
||||
from pip._internal.index.collector import parse_links
|
||||
from pip._internal.models.candidate import InstallationCandidate
|
||||
from pip._internal.models.format_control import FormatControl
|
||||
from pip._internal.models.link import Link
|
||||
|
|
@ -37,8 +36,13 @@ from pip._internal.utils.logging import indent_log
|
|||
from pip._internal.utils.misc import build_netloc
|
||||
from pip._internal.utils.packaging import check_requires_python
|
||||
from pip._internal.utils.unpacking import SUPPORTED_EXTENSIONS
|
||||
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 parse as parse_version
|
||||
|
||||
__all__ = ["FormatControl", "BestCandidateResult", "PackageFinder"]
|
||||
__all__ = ['FormatControl', 'BestCandidateResult', 'PackageFinder']
|
||||
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
|
@ -49,7 +53,7 @@ CandidateSortingKey = Tuple[int, int, int, _BaseVersion, Optional[int], BuildTag
|
|||
|
||||
def _check_link_requires_python(
|
||||
link: Link,
|
||||
version_info: Tuple[int, int, int],
|
||||
version_info: tuple[int, int, int],
|
||||
ignore_requires_python: bool = False,
|
||||
) -> bool:
|
||||
"""
|
||||
|
|
@ -68,16 +72,16 @@ def _check_link_requires_python(
|
|||
)
|
||||
except specifiers.InvalidSpecifier:
|
||||
logger.debug(
|
||||
"Ignoring invalid Requires-Python (%r) for link: %s",
|
||||
'Ignoring invalid Requires-Python (%r) for link: %s',
|
||||
link.requires_python,
|
||||
link,
|
||||
)
|
||||
else:
|
||||
if not is_compatible:
|
||||
version = ".".join(map(str, version_info))
|
||||
version = '.'.join(map(str, version_info))
|
||||
if not ignore_requires_python:
|
||||
logger.verbose(
|
||||
"Link requires a different Python (%s not in: %r): %s",
|
||||
'Link requires a different Python (%s not in: %r): %s',
|
||||
version,
|
||||
link.requires_python,
|
||||
link,
|
||||
|
|
@ -85,7 +89,7 @@ def _check_link_requires_python(
|
|||
return False
|
||||
|
||||
logger.debug(
|
||||
"Ignoring failed Requires-Python check (%s not in: %r) for link: %s",
|
||||
'Ignoring failed Requires-Python check (%s not in: %r) for link: %s',
|
||||
version,
|
||||
link.requires_python,
|
||||
link,
|
||||
|
|
@ -100,7 +104,7 @@ class LinkEvaluator:
|
|||
Responsible for evaluating links for a particular project.
|
||||
"""
|
||||
|
||||
_py_version_re = re.compile(r"-py([123]\.?[0-9]?)$")
|
||||
_py_version_re = re.compile(r'-py([123]\.?[0-9]?)$')
|
||||
|
||||
# Don't include an allow_yanked default value to make sure each call
|
||||
# site considers whether yanked releases are allowed. This also causes
|
||||
|
|
@ -110,10 +114,10 @@ class LinkEvaluator:
|
|||
self,
|
||||
project_name: str,
|
||||
canonical_name: str,
|
||||
formats: FrozenSet[str],
|
||||
formats: frozenset[str],
|
||||
target_python: TargetPython,
|
||||
allow_yanked: bool,
|
||||
ignore_requires_python: Optional[bool] = None,
|
||||
ignore_requires_python: bool | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param project_name: The user supplied package name.
|
||||
|
|
@ -143,7 +147,7 @@ class LinkEvaluator:
|
|||
|
||||
self.project_name = project_name
|
||||
|
||||
def evaluate_link(self, link: Link) -> Tuple[bool, Optional[str]]:
|
||||
def evaluate_link(self, link: Link) -> tuple[bool, str | None]:
|
||||
"""
|
||||
Determine whether a link is a candidate for installation.
|
||||
|
||||
|
|
@ -154,8 +158,8 @@ class LinkEvaluator:
|
|||
"""
|
||||
version = None
|
||||
if link.is_yanked and not self._allow_yanked:
|
||||
reason = link.yanked_reason or "<none given>"
|
||||
return (False, f"yanked for reason: {reason}")
|
||||
reason = link.yanked_reason or '<none given>'
|
||||
return (False, f'yanked for reason: {reason}')
|
||||
|
||||
if link.egg_fragment:
|
||||
egg_info = link.egg_fragment
|
||||
|
|
@ -163,21 +167,21 @@ class LinkEvaluator:
|
|||
else:
|
||||
egg_info, ext = link.splitext()
|
||||
if not ext:
|
||||
return (False, "not a file")
|
||||
return (False, 'not a file')
|
||||
if ext not in SUPPORTED_EXTENSIONS:
|
||||
return (False, f"unsupported archive format: {ext}")
|
||||
if "binary" not in self._formats and ext == WHEEL_EXTENSION:
|
||||
reason = "No binaries permitted for {}".format(self.project_name)
|
||||
return (False, f'unsupported archive format: {ext}')
|
||||
if 'binary' not in self._formats and ext == WHEEL_EXTENSION:
|
||||
reason = f'No binaries permitted for {self.project_name}'
|
||||
return (False, reason)
|
||||
if "macosx10" in link.path and ext == ".zip":
|
||||
return (False, "macosx10 one")
|
||||
if 'macosx10' in link.path and ext == '.zip':
|
||||
return (False, 'macosx10 one')
|
||||
if ext == WHEEL_EXTENSION:
|
||||
try:
|
||||
wheel = Wheel(link.filename)
|
||||
except InvalidWheelFilename:
|
||||
return (False, "invalid wheel filename")
|
||||
return (False, 'invalid wheel filename')
|
||||
if canonicalize_name(wheel.name) != self._canonical_name:
|
||||
reason = "wrong project name (not {})".format(self.project_name)
|
||||
reason = f'wrong project name (not {self.project_name})'
|
||||
return (False, reason)
|
||||
|
||||
supported_tags = self._target_python.get_tags()
|
||||
|
|
@ -187,8 +191,8 @@ class LinkEvaluator:
|
|||
file_tags = wheel.get_formatted_file_tags()
|
||||
reason = (
|
||||
"none of the wheel's tags ({}) are compatible "
|
||||
"(run pip debug --verbose to show compatible tags)".format(
|
||||
", ".join(file_tags)
|
||||
'(run pip debug --verbose to show compatible tags)'.format(
|
||||
', '.join(file_tags),
|
||||
)
|
||||
)
|
||||
return (False, reason)
|
||||
|
|
@ -196,8 +200,8 @@ class LinkEvaluator:
|
|||
version = wheel.version
|
||||
|
||||
# This should be up by the self.ok_binary check, but see issue 2700.
|
||||
if "source" not in self._formats and ext != WHEEL_EXTENSION:
|
||||
reason = f"No sources permitted for {self.project_name}"
|
||||
if 'source' not in self._formats and ext != WHEEL_EXTENSION:
|
||||
reason = f'No sources permitted for {self.project_name}'
|
||||
return (False, reason)
|
||||
|
||||
if not version:
|
||||
|
|
@ -206,7 +210,7 @@ class LinkEvaluator:
|
|||
self._canonical_name,
|
||||
)
|
||||
if not version:
|
||||
reason = f"Missing project version for {self.project_name}"
|
||||
reason = f'Missing project version for {self.project_name}'
|
||||
return (False, reason)
|
||||
|
||||
match = self._py_version_re.search(version)
|
||||
|
|
@ -214,7 +218,7 @@ class LinkEvaluator:
|
|||
version = version[: match.start()]
|
||||
py_version = match.group(1)
|
||||
if py_version != self._target_python.py_version:
|
||||
return (False, "Python version is incorrect")
|
||||
return (False, 'Python version is incorrect')
|
||||
|
||||
supports_python = _check_link_requires_python(
|
||||
link,
|
||||
|
|
@ -226,16 +230,16 @@ class LinkEvaluator:
|
|||
# _log_skipped_link().
|
||||
return (False, None)
|
||||
|
||||
logger.debug("Found link %s, version: %s", link, version)
|
||||
logger.debug('Found link %s, version: %s', link, version)
|
||||
|
||||
return (True, version)
|
||||
|
||||
|
||||
def filter_unallowed_hashes(
|
||||
candidates: List[InstallationCandidate],
|
||||
candidates: list[InstallationCandidate],
|
||||
hashes: Hashes,
|
||||
project_name: str,
|
||||
) -> List[InstallationCandidate]:
|
||||
) -> list[InstallationCandidate]:
|
||||
"""
|
||||
Filter out candidates whose hashes aren't allowed, and return a new
|
||||
list of candidates.
|
||||
|
|
@ -253,8 +257,8 @@ def filter_unallowed_hashes(
|
|||
"""
|
||||
if not hashes:
|
||||
logger.debug(
|
||||
"Given no hashes to check %s links for project %r: "
|
||||
"discarding no candidates",
|
||||
'Given no hashes to check %s links for project %r: '
|
||||
'discarding no candidates',
|
||||
len(candidates),
|
||||
project_name,
|
||||
)
|
||||
|
|
@ -284,16 +288,16 @@ def filter_unallowed_hashes(
|
|||
filtered = list(candidates)
|
||||
|
||||
if len(filtered) == len(candidates):
|
||||
discard_message = "discarding no candidates"
|
||||
discard_message = 'discarding no candidates'
|
||||
else:
|
||||
discard_message = "discarding {} non-matches:\n {}".format(
|
||||
discard_message = 'discarding {} non-matches:\n {}'.format(
|
||||
len(non_matches),
|
||||
"\n ".join(str(candidate.link) for candidate in non_matches),
|
||||
'\n '.join(str(candidate.link) for candidate in non_matches),
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
"Checked %s links for project %r against %s hashes "
|
||||
"(%s matches, %s no digest): %s",
|
||||
'Checked %s links for project %r against %s hashes '
|
||||
'(%s matches, %s no digest): %s',
|
||||
len(candidates),
|
||||
project_name,
|
||||
hashes.digest_count,
|
||||
|
|
@ -333,9 +337,9 @@ class BestCandidateResult:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
candidates: List[InstallationCandidate],
|
||||
applicable_candidates: List[InstallationCandidate],
|
||||
best_candidate: Optional[InstallationCandidate],
|
||||
candidates: list[InstallationCandidate],
|
||||
applicable_candidates: list[InstallationCandidate],
|
||||
best_candidate: InstallationCandidate | None,
|
||||
) -> None:
|
||||
"""
|
||||
:param candidates: A sequence of all available candidates found.
|
||||
|
|
@ -375,12 +379,12 @@ class CandidateEvaluator:
|
|||
def create(
|
||||
cls,
|
||||
project_name: str,
|
||||
target_python: Optional[TargetPython] = None,
|
||||
target_python: TargetPython | None = None,
|
||||
prefer_binary: bool = False,
|
||||
allow_all_prereleases: bool = False,
|
||||
specifier: Optional[specifiers.BaseSpecifier] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
) -> "CandidateEvaluator":
|
||||
specifier: specifiers.BaseSpecifier | None = None,
|
||||
hashes: Hashes | None = None,
|
||||
) -> CandidateEvaluator:
|
||||
"""Create a CandidateEvaluator object.
|
||||
|
||||
:param target_python: The target Python interpreter to use when
|
||||
|
|
@ -410,11 +414,11 @@ class CandidateEvaluator:
|
|||
def __init__(
|
||||
self,
|
||||
project_name: str,
|
||||
supported_tags: List[Tag],
|
||||
supported_tags: list[Tag],
|
||||
specifier: specifiers.BaseSpecifier,
|
||||
prefer_binary: bool = False,
|
||||
allow_all_prereleases: bool = False,
|
||||
hashes: Optional[Hashes] = None,
|
||||
hashes: Hashes | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param supported_tags: The PEP 425 tags supported by the target
|
||||
|
|
@ -435,8 +439,8 @@ class CandidateEvaluator:
|
|||
|
||||
def get_applicable_candidates(
|
||||
self,
|
||||
candidates: List[InstallationCandidate],
|
||||
) -> List[InstallationCandidate]:
|
||||
candidates: list[InstallationCandidate],
|
||||
) -> list[InstallationCandidate]:
|
||||
"""
|
||||
Return the applicable candidates from a list of candidates.
|
||||
"""
|
||||
|
|
@ -510,18 +514,18 @@ class CandidateEvaluator:
|
|||
try:
|
||||
pri = -(
|
||||
wheel.find_most_preferred_tag(
|
||||
valid_tags, self._wheel_tag_preferences
|
||||
valid_tags, self._wheel_tag_preferences,
|
||||
)
|
||||
)
|
||||
except ValueError:
|
||||
raise UnsupportedWheel(
|
||||
"{} is not a supported wheel for this platform. It "
|
||||
"can't be sorted.".format(wheel.filename)
|
||||
'{} is not a supported wheel for this platform. It '
|
||||
"can't be sorted.".format(wheel.filename),
|
||||
)
|
||||
if self._prefer_binary:
|
||||
binary_preference = 1
|
||||
if wheel.build_tag is not None:
|
||||
match = re.match(r"^(\d+)(.*)$", wheel.build_tag)
|
||||
match = re.match(r'^(\d+)(.*)$', wheel.build_tag)
|
||||
build_tag_groups = match.groups()
|
||||
build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
|
||||
else: # sdist
|
||||
|
|
@ -539,8 +543,8 @@ class CandidateEvaluator:
|
|||
|
||||
def sort_best_candidate(
|
||||
self,
|
||||
candidates: List[InstallationCandidate],
|
||||
) -> Optional[InstallationCandidate]:
|
||||
candidates: list[InstallationCandidate],
|
||||
) -> InstallationCandidate | None:
|
||||
"""
|
||||
Return the best candidate per the instance's sort order, or None if
|
||||
no candidate is acceptable.
|
||||
|
|
@ -552,7 +556,7 @@ class CandidateEvaluator:
|
|||
|
||||
def compute_best_candidate(
|
||||
self,
|
||||
candidates: List[InstallationCandidate],
|
||||
candidates: list[InstallationCandidate],
|
||||
) -> BestCandidateResult:
|
||||
"""
|
||||
Compute and return a `BestCandidateResult` instance.
|
||||
|
|
@ -581,9 +585,9 @@ class PackageFinder:
|
|||
target_python: TargetPython,
|
||||
allow_yanked: bool,
|
||||
use_deprecated_html5lib: bool,
|
||||
format_control: Optional[FormatControl] = None,
|
||||
candidate_prefs: Optional[CandidatePreferences] = None,
|
||||
ignore_requires_python: Optional[bool] = None,
|
||||
format_control: FormatControl | None = None,
|
||||
candidate_prefs: CandidatePreferences | None = None,
|
||||
ignore_requires_python: bool | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
This constructor is primarily meant to be used by the create() class
|
||||
|
|
@ -610,7 +614,7 @@ class PackageFinder:
|
|||
self.format_control = format_control
|
||||
|
||||
# These are boring links that have already been logged somehow.
|
||||
self._logged_links: Set[Link] = set()
|
||||
self._logged_links: set[Link] = set()
|
||||
|
||||
# Don't include an allow_yanked default value to make sure each call
|
||||
# site considers whether yanked releases are allowed. This also causes
|
||||
|
|
@ -621,10 +625,10 @@ class PackageFinder:
|
|||
cls,
|
||||
link_collector: LinkCollector,
|
||||
selection_prefs: SelectionPreferences,
|
||||
target_python: Optional[TargetPython] = None,
|
||||
target_python: TargetPython | None = None,
|
||||
*,
|
||||
use_deprecated_html5lib: bool,
|
||||
) -> "PackageFinder":
|
||||
) -> PackageFinder:
|
||||
"""Create a PackageFinder.
|
||||
|
||||
:param selection_prefs: The candidate selection preferences, as a
|
||||
|
|
@ -664,11 +668,11 @@ class PackageFinder:
|
|||
self._link_collector.search_scope = search_scope
|
||||
|
||||
@property
|
||||
def find_links(self) -> List[str]:
|
||||
def find_links(self) -> list[str]:
|
||||
return self._link_collector.find_links
|
||||
|
||||
@property
|
||||
def index_urls(self) -> List[str]:
|
||||
def index_urls(self) -> list[str]:
|
||||
return self.search_scope.index_urls
|
||||
|
||||
@property
|
||||
|
|
@ -703,13 +707,13 @@ class PackageFinder:
|
|||
ignore_requires_python=self._ignore_requires_python,
|
||||
)
|
||||
|
||||
def _sort_links(self, links: Iterable[Link]) -> List[Link]:
|
||||
def _sort_links(self, links: Iterable[Link]) -> list[Link]:
|
||||
"""
|
||||
Returns elements of links in order, non-egg links first, egg links
|
||||
second, while eliminating duplicates
|
||||
"""
|
||||
eggs, no_eggs = [], []
|
||||
seen: Set[Link] = set()
|
||||
seen: set[Link] = set()
|
||||
for link in links:
|
||||
if link not in seen:
|
||||
seen.add(link)
|
||||
|
|
@ -723,12 +727,12 @@ class PackageFinder:
|
|||
if link not in self._logged_links:
|
||||
# Put the link at the end so the reason is more visible and because
|
||||
# the link string is usually very long.
|
||||
logger.debug("Skipping link: %s: %s", reason, link)
|
||||
logger.debug('Skipping link: %s: %s', reason, link)
|
||||
self._logged_links.add(link)
|
||||
|
||||
def get_install_candidate(
|
||||
self, link_evaluator: LinkEvaluator, link: Link
|
||||
) -> Optional[InstallationCandidate]:
|
||||
self, link_evaluator: LinkEvaluator, link: Link,
|
||||
) -> InstallationCandidate | None:
|
||||
"""
|
||||
If the link is a candidate for install, convert it to an
|
||||
InstallationCandidate and return it. Otherwise, return None.
|
||||
|
|
@ -746,8 +750,8 @@ class PackageFinder:
|
|||
)
|
||||
|
||||
def evaluate_links(
|
||||
self, link_evaluator: LinkEvaluator, links: Iterable[Link]
|
||||
) -> List[InstallationCandidate]:
|
||||
self, link_evaluator: LinkEvaluator, links: Iterable[Link],
|
||||
) -> list[InstallationCandidate]:
|
||||
"""
|
||||
Convert links that are candidates to InstallationCandidate objects.
|
||||
"""
|
||||
|
|
@ -760,10 +764,10 @@ class PackageFinder:
|
|||
return candidates
|
||||
|
||||
def process_project_url(
|
||||
self, project_url: Link, link_evaluator: LinkEvaluator
|
||||
) -> List[InstallationCandidate]:
|
||||
self, project_url: Link, link_evaluator: LinkEvaluator,
|
||||
) -> list[InstallationCandidate]:
|
||||
logger.debug(
|
||||
"Fetching project page and analyzing links: %s",
|
||||
'Fetching project page and analyzing links: %s',
|
||||
project_url,
|
||||
)
|
||||
html_page = self._link_collector.fetch_page(project_url)
|
||||
|
|
@ -781,7 +785,7 @@ class PackageFinder:
|
|||
return package_links
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def find_all_candidates(self, project_name: str) -> List[InstallationCandidate]:
|
||||
def find_all_candidates(self, project_name: str) -> list[InstallationCandidate]:
|
||||
"""Find all available InstallationCandidate for project_name
|
||||
|
||||
This checks index_urls and find_links.
|
||||
|
|
@ -828,7 +832,7 @@ class PackageFinder:
|
|||
except Exception:
|
||||
paths.append(candidate.link.url) # it's not a local file
|
||||
|
||||
logger.debug("Local files found: %s", ", ".join(paths))
|
||||
logger.debug('Local files found: %s', ', '.join(paths))
|
||||
|
||||
# This is an intentional priority ordering
|
||||
return file_candidates + page_candidates
|
||||
|
|
@ -836,8 +840,8 @@ class PackageFinder:
|
|||
def make_candidate_evaluator(
|
||||
self,
|
||||
project_name: str,
|
||||
specifier: Optional[specifiers.BaseSpecifier] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
specifier: specifiers.BaseSpecifier | None = None,
|
||||
hashes: Hashes | None = None,
|
||||
) -> CandidateEvaluator:
|
||||
"""Create a CandidateEvaluator object to use."""
|
||||
candidate_prefs = self._candidate_prefs
|
||||
|
|
@ -854,8 +858,8 @@ class PackageFinder:
|
|||
def find_best_candidate(
|
||||
self,
|
||||
project_name: str,
|
||||
specifier: Optional[specifiers.BaseSpecifier] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
specifier: specifiers.BaseSpecifier | None = None,
|
||||
hashes: Hashes | None = None,
|
||||
) -> BestCandidateResult:
|
||||
"""Find matches for the given project and specifier.
|
||||
|
||||
|
|
@ -874,8 +878,8 @@ class PackageFinder:
|
|||
return candidate_evaluator.compute_best_candidate(candidates)
|
||||
|
||||
def find_requirement(
|
||||
self, req: InstallRequirement, upgrade: bool
|
||||
) -> Optional[InstallationCandidate]:
|
||||
self, req: InstallRequirement, upgrade: bool,
|
||||
) -> InstallationCandidate | None:
|
||||
"""Try to find a Link matching req
|
||||
|
||||
Expects req, an InstallRequirement and upgrade, a boolean
|
||||
|
|
@ -890,7 +894,7 @@ class PackageFinder:
|
|||
)
|
||||
best_candidate = best_candidate_result.best_candidate
|
||||
|
||||
installed_version: Optional[_BaseVersion] = None
|
||||
installed_version: _BaseVersion | None = None
|
||||
if req.satisfied_by is not None:
|
||||
installed_version = req.satisfied_by.version
|
||||
|
||||
|
|
@ -900,25 +904,25 @@ class PackageFinder:
|
|||
# If we stop using the pkg_resources provided specifier and start
|
||||
# using our own, we can drop the cast to str().
|
||||
return (
|
||||
", ".join(
|
||||
', '.join(
|
||||
sorted(
|
||||
{str(c.version) for c in cand_iter},
|
||||
key=parse_version,
|
||||
)
|
||||
)
|
||||
or "none"
|
||||
),
|
||||
) or
|
||||
'none'
|
||||
)
|
||||
|
||||
if installed_version is None and best_candidate is None:
|
||||
logger.critical(
|
||||
"Could not find a version that satisfies the requirement %s "
|
||||
"(from versions: %s)",
|
||||
'Could not find a version that satisfies the requirement %s '
|
||||
'(from versions: %s)',
|
||||
req,
|
||||
_format_versions(best_candidate_result.iter_all()),
|
||||
)
|
||||
|
||||
raise DistributionNotFound(
|
||||
"No matching distribution found for {}".format(req)
|
||||
f'No matching distribution found for {req}',
|
||||
)
|
||||
|
||||
best_installed = False
|
||||
|
|
@ -930,14 +934,14 @@ class PackageFinder:
|
|||
if not upgrade and installed_version is not None:
|
||||
if best_installed:
|
||||
logger.debug(
|
||||
"Existing installed version (%s) is most up-to-date and "
|
||||
"satisfies requirement",
|
||||
'Existing installed version (%s) is most up-to-date and '
|
||||
'satisfies requirement',
|
||||
installed_version,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"Existing installed version (%s) satisfies requirement "
|
||||
"(most up-to-date version is %s)",
|
||||
'Existing installed version (%s) satisfies requirement '
|
||||
'(most up-to-date version is %s)',
|
||||
installed_version,
|
||||
best_candidate.version,
|
||||
)
|
||||
|
|
@ -946,14 +950,14 @@ class PackageFinder:
|
|||
if best_installed:
|
||||
# We have an existing version, and its the best version
|
||||
logger.debug(
|
||||
"Installed version (%s) is most up-to-date (past versions: %s)",
|
||||
'Installed version (%s) is most up-to-date (past versions: %s)',
|
||||
installed_version,
|
||||
_format_versions(best_candidate_result.iter_applicable()),
|
||||
)
|
||||
raise BestVersionAlreadyInstalled
|
||||
|
||||
logger.debug(
|
||||
"Using version %s (newest of versions: %s)",
|
||||
'Using version %s (newest of versions: %s)',
|
||||
best_candidate.version,
|
||||
_format_versions(best_candidate_result.iter_applicable()),
|
||||
)
|
||||
|
|
@ -979,14 +983,14 @@ def _find_name_version_sep(fragment: str, canonical_name: str) -> int:
|
|||
# occurrences of dashes; if the string in front of it matches the canonical
|
||||
# name, this is the one separating the name and version parts.
|
||||
for i, c in enumerate(fragment):
|
||||
if c != "-":
|
||||
if c != '-':
|
||||
continue
|
||||
if canonicalize_name(fragment[:i]) == canonical_name:
|
||||
return i
|
||||
raise ValueError(f"{fragment} does not match {canonical_name}")
|
||||
raise ValueError(f'{fragment} does not match {canonical_name}')
|
||||
|
||||
|
||||
def _extract_version_from_fragment(fragment: str, canonical_name: str) -> Optional[str]:
|
||||
def _extract_version_from_fragment(fragment: str, canonical_name: str) -> str | None:
|
||||
"""Parse the version string from a <package>+<version> filename
|
||||
"fragment" (stem) or egg fragment.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import pathlib
|
||||
from typing import Callable, Iterable, Optional, Tuple
|
||||
from typing import Callable
|
||||
from typing import Iterable
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.models.candidate import InstallationCandidate
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.utils.urls import path_to_url, url_to_path
|
||||
from pip._internal.utils.urls import path_to_url
|
||||
from pip._internal.utils.urls import url_to_path
|
||||
from pip._internal.vcs import is_url
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -19,7 +25,7 @@ PageValidator = Callable[[Link], bool]
|
|||
|
||||
class LinkSource:
|
||||
@property
|
||||
def link(self) -> Optional[Link]:
|
||||
def link(self) -> Link | None:
|
||||
"""Returns the underlying link, if there's one."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -33,7 +39,7 @@ class LinkSource:
|
|||
|
||||
|
||||
def _is_html_file(file_url: str) -> bool:
|
||||
return mimetypes.guess_type(file_url, strict=False)[0] == "text/html"
|
||||
return mimetypes.guess_type(file_url, strict=False)[0] == 'text/html'
|
||||
|
||||
|
||||
class _FlatDirectorySource(LinkSource):
|
||||
|
|
@ -54,7 +60,7 @@ class _FlatDirectorySource(LinkSource):
|
|||
self._path = pathlib.Path(os.path.realpath(path))
|
||||
|
||||
@property
|
||||
def link(self) -> Optional[Link]:
|
||||
def link(self) -> Link | None:
|
||||
return None
|
||||
|
||||
def page_candidates(self) -> FoundCandidates:
|
||||
|
|
@ -91,7 +97,7 @@ class _LocalFileSource(LinkSource):
|
|||
self._link = link
|
||||
|
||||
@property
|
||||
def link(self) -> Optional[Link]:
|
||||
def link(self) -> Link | None:
|
||||
return self._link
|
||||
|
||||
def page_candidates(self) -> FoundCandidates:
|
||||
|
|
@ -125,7 +131,7 @@ class _RemoteFileSource(LinkSource):
|
|||
self._link = link
|
||||
|
||||
@property
|
||||
def link(self) -> Optional[Link]:
|
||||
def link(self) -> Link | None:
|
||||
return self._link
|
||||
|
||||
def page_candidates(self) -> FoundCandidates:
|
||||
|
|
@ -153,7 +159,7 @@ class _IndexDirectorySource(LinkSource):
|
|||
self._link = link
|
||||
|
||||
@property
|
||||
def link(self) -> Optional[Link]:
|
||||
def link(self) -> Link | None:
|
||||
return self._link
|
||||
|
||||
def page_candidates(self) -> FoundCandidates:
|
||||
|
|
@ -170,14 +176,14 @@ def build_source(
|
|||
page_validator: PageValidator,
|
||||
expand_dir: bool,
|
||||
cache_link_parsing: bool,
|
||||
) -> Tuple[Optional[str], Optional[LinkSource]]:
|
||||
) -> tuple[str | None, LinkSource | None]:
|
||||
|
||||
path: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
path: str | None = None
|
||||
url: str | None = None
|
||||
if os.path.exists(location): # Is a local path.
|
||||
url = path_to_url(location)
|
||||
path = location
|
||||
elif location.startswith("file:"): # A file: URL.
|
||||
elif location.startswith('file:'): # A file: URL.
|
||||
url = location
|
||||
path = url_to_path(location)
|
||||
elif is_url(location):
|
||||
|
|
@ -186,7 +192,7 @@ def build_source(
|
|||
if url is None:
|
||||
msg = (
|
||||
"Location '%s' is ignored: "
|
||||
"it is either a non-existing path or lacks a specific scheme."
|
||||
'it is either a non-existing path or lacks a specific scheme.'
|
||||
)
|
||||
logger.warning(msg, location)
|
||||
return (None, None)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue