Voice et bot modif

This commit is contained in:
pi 2026-06-16 17:09:34 +00:00
parent 189d56026b
commit 7333a22bcd
10774 changed files with 634644 additions and 933308 deletions

View file

@ -1 +1,2 @@
"""A package that contains models that represent entities."""
"""A package that contains models that represent entities.
"""

View file

@ -1,25 +1,30 @@
from dataclasses import dataclass
from pip._vendor.packaging.version import Version
from pip._vendor.packaging.version import parse as parse_version
from pip._internal.models.link import Link
from pip._internal.utils.models import KeyBasedCompareMixin
@dataclass(frozen=True)
class InstallationCandidate:
class InstallationCandidate(KeyBasedCompareMixin):
"""Represents a potential "candidate" for installation."""
__slots__ = ["name", "version", "link"]
name: str
version: Version
link: Link
def __init__(self, name: str, version: str, link: Link) -> None:
object.__setattr__(self, "name", name)
object.__setattr__(self, "version", parse_version(version))
object.__setattr__(self, "link", link)
self.name = name
self.version = parse_version(version)
self.link = link
super().__init__(
key=(self.name, self.version, self.link),
defining_class=InstallationCandidate,
)
def __repr__(self) -> str:
return "<InstallationCandidate({!r}, {!r}, {!r})>".format(
self.name,
self.version,
self.link,
)
def __str__(self) -> str:
return f"{self.name!r} candidate (version {self.version} at {self.link})"

View file

@ -1,13 +1,8 @@
"""PEP 610"""
from __future__ import annotations
""" PEP 610 """
import json
import re
import urllib.parse
from collections.abc import Iterable
from dataclasses import dataclass
from typing import Any, ClassVar, TypeVar, Union
from typing import Any, Dict, Iterable, Optional, Type, TypeVar, Union
__all__ = [
"DirectUrl",
@ -28,8 +23,8 @@ class DirectUrlValidationError(Exception):
def _get(
d: dict[str, Any], expected_type: type[T], key: str, default: T | None = None
) -> T | None:
d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None
) -> Optional[T]:
"""Get value from dictionary and verify expected type."""
if key not in d:
return default
@ -42,7 +37,7 @@ def _get(
def _get_required(
d: dict[str, Any], expected_type: type[T], key: str, default: T | None = None
d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None
) -> T:
value = _get(d, expected_type, key, default)
if value is None:
@ -50,7 +45,7 @@ def _get_required(
return value
def _exactly_one_of(infos: Iterable[InfoType | None]) -> InfoType:
def _exactly_one_of(infos: Iterable[Optional["InfoType"]]) -> "InfoType":
infos = [info for info in infos if info is not None]
if not infos:
raise DirectUrlValidationError(
@ -64,21 +59,26 @@ def _exactly_one_of(infos: Iterable[InfoType | None]) -> InfoType:
return infos[0]
def _filter_none(**kwargs: Any) -> dict[str, Any]:
def _filter_none(**kwargs: Any) -> Dict[str, Any]:
"""Make dict excluding None values."""
return {k: v for k, v in kwargs.items() if v is not None}
@dataclass
class VcsInfo:
name: ClassVar = "vcs_info"
name = "vcs_info"
vcs: str
commit_id: str
requested_revision: str | None = None
def __init__(
self,
vcs: str,
commit_id: str,
requested_revision: Optional[str] = None,
) -> None:
self.vcs = vcs
self.requested_revision = requested_revision
self.commit_id = commit_id
@classmethod
def _from_dict(cls, d: dict[str, Any] | None) -> VcsInfo | None:
def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["VcsInfo"]:
if d is None:
return None
return cls(
@ -87,7 +87,7 @@ class VcsInfo:
requested_revision=_get(d, str, "requested_revision"),
)
def _to_dict(self) -> dict[str, Any]:
def _to_dict(self) -> Dict[str, Any]:
return _filter_none(
vcs=self.vcs,
requested_revision=self.requested_revision,
@ -100,19 +100,19 @@ class ArchiveInfo:
def __init__(
self,
hash: str | None = None,
hashes: dict[str, str] | None = None,
hash: Optional[str] = None,
hashes: Optional[Dict[str, str]] = None,
) -> None:
# set hashes before hash, since the hash setter will further populate hashes
self.hashes = hashes
self.hash = hash
@property
def hash(self) -> str | None:
def hash(self) -> Optional[str]:
return self._hash
@hash.setter
def hash(self, value: str | None) -> None:
def hash(self, value: Optional[str]) -> None:
if value is not None:
# Auto-populate the hashes key to upgrade to the new format automatically.
# We don't back-populate the legacy hash key from hashes.
@ -130,39 +130,47 @@ class ArchiveInfo:
self._hash = value
@classmethod
def _from_dict(cls, d: dict[str, Any] | None) -> ArchiveInfo | None:
def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["ArchiveInfo"]:
if d is None:
return None
return cls(hash=_get(d, str, "hash"), hashes=_get(d, dict, "hashes"))
def _to_dict(self) -> dict[str, Any]:
def _to_dict(self) -> Dict[str, Any]:
return _filter_none(hash=self.hash, hashes=self.hashes)
@dataclass
class DirInfo:
name: ClassVar = "dir_info"
name = "dir_info"
editable: bool = False
def __init__(
self,
editable: bool = False,
) -> None:
self.editable = editable
@classmethod
def _from_dict(cls, d: dict[str, Any] | None) -> DirInfo | None:
def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["DirInfo"]:
if d is None:
return None
return cls(editable=_get_required(d, bool, "editable", default=False))
def _to_dict(self) -> dict[str, Any]:
def _to_dict(self) -> Dict[str, Any]:
return _filter_none(editable=self.editable or None)
InfoType = Union[ArchiveInfo, DirInfo, VcsInfo]
@dataclass
class DirectUrl:
url: str
info: InfoType
subdirectory: str | None = None
def __init__(
self,
url: str,
info: InfoType,
subdirectory: Optional[str] = None,
) -> None:
self.url = url
self.info = info
self.subdirectory = subdirectory
def _remove_auth_from_netloc(self, netloc: str) -> str:
if "@" not in netloc:
@ -195,7 +203,7 @@ class DirectUrl:
self.from_dict(self.to_dict())
@classmethod
def from_dict(cls, d: dict[str, Any]) -> DirectUrl:
def from_dict(cls, d: Dict[str, Any]) -> "DirectUrl":
return DirectUrl(
url=_get_required(d, str, "url"),
subdirectory=_get(d, str, "subdirectory"),
@ -208,7 +216,7 @@ class DirectUrl:
),
)
def to_dict(self) -> dict[str, Any]:
def to_dict(self) -> Dict[str, Any]:
res = _filter_none(
url=self.redacted_url,
subdirectory=self.subdirectory,
@ -217,7 +225,7 @@ class DirectUrl:
return res
@classmethod
def from_json(cls, s: str) -> DirectUrl:
def from_json(cls, s: str) -> "DirectUrl":
return cls.from_dict(json.loads(s))
def to_json(self) -> str:

View file

@ -1,4 +1,4 @@
from __future__ import annotations
from typing import FrozenSet, Optional, Set
from pip._vendor.packaging.utils import canonicalize_name
@ -12,8 +12,8 @@ class FormatControl:
def __init__(
self,
no_binary: set[str] | None = None,
only_binary: set[str] | None = None,
no_binary: Optional[Set[str]] = None,
only_binary: Optional[Set[str]] = None,
) -> None:
if no_binary is None:
no_binary = set()
@ -36,7 +36,7 @@ class FormatControl:
return f"{self.__class__.__name__}({self.no_binary}, {self.only_binary})"
@staticmethod
def handle_mutual_excludes(value: str, target: set[str], other: set[str]) -> None:
def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> None:
if value.startswith("-"):
raise CommandError(
"--no-binary / --only-binary option requires 1 argument."
@ -58,7 +58,7 @@ class FormatControl:
other.discard(name)
target.add(name)
def get_allowed_formats(self, canonical_name: str) -> frozenset[str]:
def get_allowed_formats(self, canonical_name: str) -> FrozenSet[str]:
result = {"binary", "source"}
if canonical_name in self.only_binary:
result.discard("source")

View file

@ -1,5 +1,4 @@
from collections.abc import Sequence
from typing import Any
from typing import Any, Dict, Sequence
from pip._vendor.packaging.markers import default_environment
@ -12,7 +11,7 @@ class InstallationReport:
self._install_requirements = install_requirements
@classmethod
def _install_req_to_dict(cls, ireq: InstallRequirement) -> dict[str, Any]:
def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]:
assert ireq.download_info, f"No download_info for {ireq}"
res = {
# PEP 610 json for the download URL. download_info.archive_info.hashes may
@ -40,7 +39,7 @@ class InstallationReport:
res["requested_extras"] = sorted(ireq.extras)
return res
def to_dict(self) -> dict[str, Any]:
def to_dict(self) -> Dict[str, Any]:
return {
"version": "1",
"pip_version": __version__,

View file

@ -1,5 +1,3 @@
from __future__ import annotations
import functools
import itertools
import logging
@ -7,12 +5,17 @@ import os
import posixpath
import re
import urllib.parse
from collections.abc import Mapping
from dataclasses import dataclass
from typing import (
TYPE_CHECKING,
Any,
Dict,
List,
Mapping,
NamedTuple,
Optional,
Tuple,
Union,
)
from pip._internal.utils.deprecation import deprecated
@ -24,6 +27,7 @@ from pip._internal.utils.misc import (
split_auth_from_netloc,
splitext,
)
from pip._internal.utils.models import KeyBasedCompareMixin
from pip._internal.utils.urls import path_to_url, url_to_path
if TYPE_CHECKING:
@ -66,8 +70,8 @@ class LinkHash:
assert self.name in _SUPPORTED_HASHES
@classmethod
@functools.cache
def find_hash_url_fragment(cls, url: str) -> LinkHash | None:
@functools.lru_cache(maxsize=None)
def find_hash_url_fragment(cls, url: str) -> Optional["LinkHash"]:
"""Search a string for a checksum algorithm name and encoded output value."""
match = cls._hash_url_fragment_re.search(url)
if match is None:
@ -75,14 +79,14 @@ class LinkHash:
name, value = match.groups()
return cls(name=name, value=value)
def as_dict(self) -> dict[str, str]:
def as_dict(self) -> Dict[str, str]:
return {self.name: self.value}
def as_hashes(self) -> Hashes:
"""Return a Hashes instance which checks only for the current hash."""
return Hashes({self.name: [self.value]})
def is_hash_allowed(self, hashes: Hashes | None) -> bool:
def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
"""
Return True if the current hash is allowed by `hashes`.
"""
@ -95,14 +99,14 @@ class LinkHash:
class MetadataFile:
"""Information about a core metadata file associated with a distribution."""
hashes: dict[str, str] | None
hashes: Optional[Dict[str, str]]
def __post_init__(self) -> None:
if self.hashes is not None:
assert all(name in _SUPPORTED_HASHES for name in self.hashes)
def supported_hashes(hashes: dict[str, str] | None) -> dict[str, str] | None:
def supported_hashes(hashes: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
# Remove any unsupported hash types from the mapping. If this leaves no
# supported hashes, return None
if hashes is None:
@ -131,11 +135,7 @@ def _clean_file_url_path(part: str) -> str:
# should not be quoted. On Linux where drive letters do not
# exist, the colon should be quoted. We rely on urllib.request
# to do the right thing here.
ret = urllib.request.pathname2url(urllib.request.url2pathname(part))
if ret.startswith("///"):
# Remove any URL authority section, leaving only the URL path.
ret = ret.removeprefix("//")
return ret
return urllib.request.pathname2url(urllib.request.url2pathname(part))
# percent-encoded: /
@ -171,37 +171,20 @@ def _ensure_quoted_url(url: str) -> str:
and without double-quoting other characters.
"""
# Split the URL into parts according to the general structure
# `scheme://netloc/path?query#fragment`.
result = urllib.parse.urlsplit(url)
# `scheme://netloc/path;parameters?query#fragment`.
result = urllib.parse.urlparse(url)
# If the netloc is empty, then the URL refers to a local filesystem path.
is_local_path = not result.netloc
path = _clean_url_path(result.path, is_local_path=is_local_path)
# Temporarily replace scheme with file to ensure the URL generated by
# urlunsplit() contains an empty netloc (file://) as per RFC 1738.
ret = urllib.parse.urlunsplit(result._replace(scheme="file", path=path))
ret = result.scheme + ret[4:] # Restore original scheme.
return ret
return urllib.parse.urlunparse(result._replace(path=path))
def _absolute_link_url(base_url: str, url: str) -> str:
"""
A faster implementation of urllib.parse.urljoin with a shortcut
for absolute http/https URLs.
"""
if url.startswith(("https://", "http://")):
return url
else:
return urllib.parse.urljoin(base_url, url)
@functools.total_ordering
class Link:
class Link(KeyBasedCompareMixin):
"""Represents a parsed link from a Package Index's simple URL"""
__slots__ = [
"_parsed_url",
"_url",
"_path",
"_hashes",
"comes_from",
"requires_python",
@ -214,12 +197,12 @@ class Link:
def __init__(
self,
url: str,
comes_from: str | IndexContent | None = None,
requires_python: str | None = None,
yanked_reason: str | None = None,
metadata_file_data: MetadataFile | None = None,
comes_from: Optional[Union[str, "IndexContent"]] = None,
requires_python: Optional[str] = None,
yanked_reason: Optional[str] = None,
metadata_file_data: Optional[MetadataFile] = None,
cache_link_parsing: bool = True,
hashes: Mapping[str, str] | None = None,
hashes: Optional[Mapping[str, str]] = None,
) -> None:
"""
:param url: url of the resource pointed to (href of the link)
@ -258,8 +241,6 @@ class Link:
# Store the url as a private attribute to prevent accidentally
# trying to set a new value.
self._url = url
# The .path property is hot, so calculate its value ahead of time.
self._path = urllib.parse.unquote(self._parsed_url.path)
link_hash = LinkHash.find_hash_url_fragment(url)
hashes_from_link = {} if link_hash is None else link_hash.as_dict()
@ -273,15 +254,17 @@ class Link:
self.yanked_reason = yanked_reason
self.metadata_file_data = metadata_file_data
super().__init__(key=url, defining_class=Link)
self.cache_link_parsing = cache_link_parsing
self.egg_fragment = self._egg_fragment()
@classmethod
def from_json(
cls,
file_data: dict[str, Any],
file_data: Dict[str, Any],
page_url: str,
) -> Link | None:
) -> Optional["Link"]:
"""
Convert an pypi json document from a simple repository page into a Link.
"""
@ -289,7 +272,7 @@ class Link:
if file_url is None:
return None
url = _ensure_quoted_url(_absolute_link_url(page_url, file_url))
url = _ensure_quoted_url(urllib.parse.urljoin(page_url, file_url))
pyrequire = file_data.get("requires-python")
yanked_reason = file_data.get("yanked")
hashes = file_data.get("hashes", {})
@ -330,10 +313,10 @@ class Link:
@classmethod
def from_element(
cls,
anchor_attribs: dict[str, str | None],
anchor_attribs: Dict[str, Optional[str]],
page_url: str,
base_url: str,
) -> Link | None:
) -> Optional["Link"]:
"""
Convert an anchor element's attributes in a simple repository page to a Link.
"""
@ -341,7 +324,7 @@ class Link:
if not href:
return None
url = _ensure_quoted_url(_absolute_link_url(base_url, href))
url = _ensure_quoted_url(urllib.parse.urljoin(base_url, href))
pyrequire = anchor_attribs.get("data-requires-python")
yanked_reason = anchor_attribs.get("data-yanked")
@ -385,34 +368,17 @@ class Link:
else:
rp = ""
if self.comes_from:
return f"{self.redacted_url} (from {self.comes_from}){rp}"
return f"{redact_auth_from_url(self._url)} (from {self.comes_from}){rp}"
else:
return self.redacted_url
return redact_auth_from_url(str(self._url))
def __repr__(self) -> str:
return f"<Link {self}>"
def __hash__(self) -> int:
return hash(self.url)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Link):
return NotImplemented
return self.url == other.url
def __lt__(self, other: Any) -> bool:
if not isinstance(other, Link):
return NotImplemented
return self.url < other.url
@property
def url(self) -> str:
return self._url
@property
def redacted_url(self) -> str:
return redact_auth_from_url(self.url)
@property
def filename(self) -> str:
path = self.path.rstrip("/")
@ -444,9 +410,9 @@ class Link:
@property
def path(self) -> str:
return self._path
return urllib.parse.unquote(self._parsed_url.path)
def splitext(self) -> tuple[str, str]:
def splitext(self) -> Tuple[str, str]:
return splitext(posixpath.basename(self.path.rstrip("/")))
@property
@ -465,7 +431,7 @@ class Link:
r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
)
def _egg_fragment(self) -> str | None:
def _egg_fragment(self) -> Optional[str]:
match = self._egg_fragment_re.search(self._url)
if not match:
return None
@ -475,10 +441,10 @@ class Link:
project_name = match.group(1)
if not self._project_name_re.match(project_name):
deprecated(
reason=f"{self} contains an egg fragment with a non-PEP 508 name.",
reason=f"{self} contains an egg fragment with a non-PEP 508 name",
replacement="to use the req @ url syntax, and remove the egg fragment",
gone_in="26.0",
issue=13157,
gone_in="25.0",
issue=11617,
)
return project_name
@ -486,13 +452,13 @@ class Link:
_subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")
@property
def subdirectory_fragment(self) -> str | None:
def subdirectory_fragment(self) -> Optional[str]:
match = self._subdirectory_fragment_re.search(self._url)
if not match:
return None
return match.group(1)
def metadata_link(self) -> Link | None:
def metadata_link(self) -> Optional["Link"]:
"""Return a link to the associated core metadata file (if any)."""
if self.metadata_file_data is None:
return None
@ -505,11 +471,11 @@ class Link:
return Hashes({k: [v] for k, v in self._hashes.items()})
@property
def hash(self) -> str | None:
def hash(self) -> Optional[str]:
return next(iter(self._hashes.values()), None)
@property
def hash_name(self) -> str | None:
def hash_name(self) -> Optional[str]:
return next(iter(self._hashes), None)
@property
@ -541,7 +507,7 @@ class Link:
def has_hash(self) -> bool:
return bool(self._hashes)
def is_hash_allowed(self, hashes: Hashes | None) -> bool:
def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
"""
Return True if the link has a hash and it is allowed by `hashes`.
"""
@ -577,9 +543,9 @@ class _CleanResult(NamedTuple):
"""
parsed: urllib.parse.SplitResult
query: dict[str, list[str]]
query: Dict[str, List[str]]
subdirectory: str
hashes: dict[str, str]
hashes: Dict[str, str]
def _clean_link(link: Link) -> _CleanResult:
@ -608,6 +574,6 @@ def _clean_link(link: Link) -> _CleanResult:
)
@functools.cache
@functools.lru_cache(maxsize=None)
def links_equivalent(link1: Link, link2: Link) -> bool:
return _clean_link(link1) == _clean_link(link2)

View file

@ -1,188 +0,0 @@
from __future__ import annotations
import dataclasses
import re
from collections.abc import Iterable
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any
from pip._vendor import tomli_w
from pip._internal.models.direct_url import ArchiveInfo, DirInfo, VcsInfo
from pip._internal.models.link import Link
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.urls import url_to_path
if TYPE_CHECKING:
from typing_extensions import Self
PYLOCK_FILE_NAME_RE = re.compile(r"^pylock\.([^.]+)\.toml$")
def is_valid_pylock_file_name(path: Path) -> bool:
return path.name == "pylock.toml" or bool(re.match(PYLOCK_FILE_NAME_RE, path.name))
def _toml_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:
return {key.replace("_", "-"): value for key, value in data if value is not None}
@dataclass
class PackageVcs:
type: str
url: str | None
# (not supported) path: Optional[str]
requested_revision: str | None
commit_id: str
subdirectory: str | None
@dataclass
class PackageDirectory:
path: str
editable: bool | None
subdirectory: str | None
@dataclass
class PackageArchive:
url: str | None
# (not supported) path: Optional[str]
# (not supported) size: Optional[int]
# (not supported) upload_time: Optional[datetime]
hashes: dict[str, str]
subdirectory: str | None
@dataclass
class PackageSdist:
name: str
# (not supported) upload_time: Optional[datetime]
url: str | None
# (not supported) path: Optional[str]
# (not supported) size: Optional[int]
hashes: dict[str, str]
@dataclass
class PackageWheel:
name: str
# (not supported) upload_time: Optional[datetime]
url: str | None
# (not supported) path: Optional[str]
# (not supported) size: Optional[int]
hashes: dict[str, str]
@dataclass
class Package:
name: str
version: str | None = None
# (not supported) marker: Optional[str]
# (not supported) requires_python: Optional[str]
# (not supported) dependencies
vcs: PackageVcs | None = None
directory: PackageDirectory | None = None
archive: PackageArchive | None = None
# (not supported) index: Optional[str]
sdist: PackageSdist | None = None
wheels: list[PackageWheel] | None = None
# (not supported) attestation_identities: Optional[List[Dict[str, Any]]]
# (not supported) tool: Optional[Dict[str, Any]]
@classmethod
def from_install_requirement(cls, ireq: InstallRequirement, base_dir: Path) -> Self:
base_dir = base_dir.resolve()
dist = ireq.get_dist()
download_info = ireq.download_info
assert download_info
package = cls(name=dist.canonical_name)
if ireq.is_direct:
if isinstance(download_info.info, VcsInfo):
package.vcs = PackageVcs(
type=download_info.info.vcs,
url=download_info.url,
requested_revision=download_info.info.requested_revision,
commit_id=download_info.info.commit_id,
subdirectory=download_info.subdirectory,
)
elif isinstance(download_info.info, DirInfo):
package.directory = PackageDirectory(
path=(
Path(url_to_path(download_info.url))
.resolve()
.relative_to(base_dir)
.as_posix()
),
editable=(
download_info.info.editable
if download_info.info.editable
else None
),
subdirectory=download_info.subdirectory,
)
elif isinstance(download_info.info, ArchiveInfo):
if not download_info.info.hashes:
raise NotImplementedError()
package.archive = PackageArchive(
url=download_info.url,
hashes=download_info.info.hashes,
subdirectory=download_info.subdirectory,
)
else:
# should never happen
raise NotImplementedError()
else:
package.version = str(dist.version)
if isinstance(download_info.info, ArchiveInfo):
if not download_info.info.hashes:
raise NotImplementedError()
link = Link(download_info.url)
if link.is_wheel:
package.wheels = [
PackageWheel(
name=link.filename,
url=download_info.url,
hashes=download_info.info.hashes,
)
]
else:
package.sdist = PackageSdist(
name=link.filename,
url=download_info.url,
hashes=download_info.info.hashes,
)
else:
# should never happen
raise NotImplementedError()
return package
@dataclass
class Pylock:
lock_version: str = "1.0"
# (not supported) environments: Optional[List[str]]
# (not supported) requires_python: Optional[str]
# (not supported) extras: List[str] = []
# (not supported) dependency_groups: List[str] = []
created_by: str = "pip"
packages: list[Package] = dataclasses.field(default_factory=list)
# (not supported) tool: Optional[Dict[str, Any]]
def as_toml(self) -> str:
return tomli_w.dumps(dataclasses.asdict(self, dict_factory=_toml_dict_factory))
@classmethod
def from_install_requirements(
cls, install_requirements: Iterable[InstallRequirement], base_dir: Path
) -> Self:
return cls(
packages=sorted(
(
Package.from_install_requirement(ireq, base_dir)
for ireq in install_requirements
),
key=lambda p: p.name,
)
)

View file

@ -5,12 +5,10 @@ For a general overview of available schemes and their context, see
https://docs.python.org/3/install/index.html#alternate-installation.
"""
from dataclasses import dataclass
SCHEME_KEYS = ["platlib", "purelib", "headers", "scripts", "data"]
@dataclass(frozen=True)
class Scheme:
"""A Scheme holds paths which are used as the base directories for
artifacts associated with a Python package.
@ -18,8 +16,16 @@ class Scheme:
__slots__ = SCHEME_KEYS
platlib: str
purelib: str
headers: str
scripts: str
data: str
def __init__(
self,
platlib: str,
purelib: str,
headers: str,
scripts: str,
data: str,
) -> None:
self.platlib = platlib
self.purelib = purelib
self.headers = headers
self.scripts = scripts
self.data = data

View file

@ -3,7 +3,7 @@ import logging
import os
import posixpath
import urllib.parse
from dataclasses import dataclass
from typing import List
from pip._vendor.packaging.utils import canonicalize_name
@ -14,23 +14,19 @@ from pip._internal.utils.misc import normalize_path, redact_auth_from_url
logger = logging.getLogger(__name__)
@dataclass(frozen=True)
class SearchScope:
"""
Encapsulates the locations that pip is configured to search.
"""
__slots__ = ["find_links", "index_urls", "no_index"]
find_links: list[str]
index_urls: list[str]
no_index: bool
@classmethod
def create(
cls,
find_links: list[str],
index_urls: list[str],
find_links: List[str],
index_urls: List[str],
no_index: bool,
) -> "SearchScope":
"""
@ -41,7 +37,7 @@ class SearchScope:
# it and if it exists, use the normalized version.
# This is deliberately conservative - it might be fine just to
# blindly normalize anything starting with a ~...
built_find_links: list[str] = []
built_find_links: List[str] = []
for link in find_links:
if link.startswith("~"):
new_link = normalize_path(link)
@ -68,6 +64,16 @@ class SearchScope:
no_index=no_index,
)
def __init__(
self,
find_links: List[str],
index_urls: List[str],
no_index: bool,
) -> None:
self.find_links = find_links
self.index_urls = index_urls
self.no_index = no_index
def get_formatted_locations(self) -> str:
lines = []
redacted_index_urls = []
@ -103,7 +109,7 @@ class SearchScope:
)
return "\n".join(lines)
def get_index_urls_locations(self, project_name: str) -> list[str]:
def get_index_urls_locations(self, project_name: str) -> List[str]:
"""Returns the locations found via self.index_urls
Checks the url_name on the main (first in the list) index and

View file

@ -1,10 +1,8 @@
from __future__ import annotations
from typing import Optional
from pip._internal.models.format_control import FormatControl
# TODO: This needs Python 3.10's improved slots support for dataclasses
# to be converted into a dataclass.
class SelectionPreferences:
"""
Encapsulates the candidate selection preferences for downloading
@ -27,9 +25,9 @@ class SelectionPreferences:
self,
allow_yanked: bool,
allow_all_prereleases: bool = False,
format_control: FormatControl | None = None,
format_control: Optional[FormatControl] = None,
prefer_binary: bool = False,
ignore_requires_python: bool | None = None,
ignore_requires_python: Optional[bool] = None,
) -> None:
"""Create a SelectionPreferences object.

View file

@ -1,6 +1,5 @@
from __future__ import annotations
import sys
from typing import List, Optional, Set, Tuple
from pip._vendor.packaging.tags import Tag
@ -9,6 +8,7 @@ from pip._internal.utils.misc import normalize_version_info
class TargetPython:
"""
Encapsulates the properties of a Python interpreter one is targeting
for a package install, download, etc.
@ -27,10 +27,10 @@ class TargetPython:
def __init__(
self,
platforms: list[str] | None = None,
py_version_info: tuple[int, ...] | None = None,
abis: list[str] | None = None,
implementation: str | None = None,
platforms: Optional[List[str]] = None,
py_version_info: Optional[Tuple[int, ...]] = None,
abis: Optional[List[str]] = None,
implementation: Optional[str] = None,
) -> None:
"""
:param platforms: A list of strings or None. If None, searches for
@ -63,8 +63,8 @@ class TargetPython:
self.py_version_info = py_version_info
# This is used to cache the return value of get_(un)sorted_tags.
self._valid_tags: list[Tag] | None = None
self._valid_tags_set: set[Tag] | None = None
self._valid_tags: Optional[List[Tag]] = None
self._valid_tags_set: Optional[Set[Tag]] = None
def format_given(self) -> str:
"""
@ -86,7 +86,7 @@ class TargetPython:
f"{key}={value!r}" for key, value in key_values if value is not None
)
def get_sorted_tags(self) -> list[Tag]:
def get_sorted_tags(self) -> List[Tag]:
"""
Return the supported PEP 425 tags to check wheel candidates against.
@ -111,7 +111,7 @@ class TargetPython:
return self._valid_tags
def get_unsorted_tags(self) -> set[Tag]:
def get_unsorted_tags(self) -> Set[Tag]:
"""Exactly the same as get_sorted_tags, but returns a set.
This is important for performance.

View file

@ -1,16 +1,10 @@
"""Represents a wheel file and provides access to the various parts of the
name that have meaning.
"""
from __future__ import annotations
from collections.abc import Iterable
import re
from typing import Dict, Iterable, List
from pip._vendor.packaging.tags import Tag
from pip._vendor.packaging.utils import (
InvalidWheelFilename as _PackagingInvalidWheelFilename,
)
from pip._vendor.packaging.utils import parse_wheel_filename
from pip._internal.exceptions import InvalidWheelFilename
@ -18,22 +12,40 @@ from pip._internal.exceptions import InvalidWheelFilename
class Wheel:
"""A wheel file"""
wheel_file_re = re.compile(
r"""^(?P<namever>(?P<name>[^\s-]+?)-(?P<ver>[^\s-]*?))
((-(?P<build>\d[^-]*?))?-(?P<pyver>[^\s-]+?)-(?P<abi>[^\s-]+?)-(?P<plat>[^\s-]+?)
\.whl|\.dist-info)$""",
re.VERBOSE,
)
def __init__(self, filename: str) -> None:
"""
:raises InvalidWheelFilename: when the filename is invalid for a wheel
"""
wheel_info = self.wheel_file_re.match(filename)
if not wheel_info:
raise InvalidWheelFilename(f"{filename} is not a valid wheel filename.")
self.filename = filename
self.name = wheel_info.group("name").replace("_", "-")
# we'll assume "_" means "-" due to wheel naming scheme
# (https://github.com/pypa/pip/issues/1150)
self.version = wheel_info.group("ver").replace("_", "-")
self.build_tag = wheel_info.group("build")
self.pyversions = wheel_info.group("pyver").split(".")
self.abis = wheel_info.group("abi").split(".")
self.plats = wheel_info.group("plat").split(".")
try:
wheel_info = parse_wheel_filename(filename)
except _PackagingInvalidWheelFilename as e:
raise InvalidWheelFilename(e.args[0]) from None
# All the tag combinations from this file
self.file_tags = {
Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats
}
self.name, _version, self.build_tag, self.file_tags = wheel_info
self.version = str(_version)
def get_formatted_file_tags(self) -> list[str]:
def get_formatted_file_tags(self) -> List[str]:
"""Return the wheel's tags as a sorted list of strings."""
return sorted(str(tag) for tag in self.file_tags)
def support_index_min(self, tags: list[Tag]) -> int:
def support_index_min(self, tags: List[Tag]) -> int:
"""Return the lowest index that one of the wheel's file_tag combinations
achieves in the given list of supported tags.
@ -52,7 +64,7 @@ class Wheel:
raise ValueError()
def find_most_preferred_tag(
self, tags: list[Tag], tag_to_priority: dict[Tag, int]
self, tags: List[Tag], tag_to_priority: Dict[Tag, int]
) -> int:
"""Return the priority of the most preferred tag that one of the wheel's file
tag combinations achieves in the given list of supported tags using the given