mirror of
https://github.com/pre-commit/pre-commit-hooks.git
synced 2026-04-09 04:54:16 +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,9 +1,12 @@
|
|||
from typing import List, Optional
|
||||
from __future__ import annotations
|
||||
|
||||
__version__ = "22.0.4"
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
__version__ = '22.0.4'
|
||||
|
||||
|
||||
def main(args: Optional[List[str]] = None) -> int:
|
||||
def main(args: list[str] | None = None) -> int:
|
||||
"""This is an internal API only meant for use by pip's own console scripts.
|
||||
|
||||
For additional details, see https://github.com/pypa/pip/issues/7498.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
|
|
@ -6,12 +8,12 @@ import warnings
|
|||
# of sys.path, if present to avoid using current directory
|
||||
# in pip commands check, freeze, install, list and show,
|
||||
# when invoked as python -m pip <command>
|
||||
if sys.path[0] in ("", os.getcwd()):
|
||||
if sys.path[0] in ('', os.getcwd()):
|
||||
sys.path.pop(0)
|
||||
|
||||
# If we are running from a wheel, add the wheel to sys.path
|
||||
# This allows the usage python pip-*.whl/pip install pip-*.whl
|
||||
if __package__ == "":
|
||||
if __package__ == '':
|
||||
# __file__ is pip-*.whl/pip/__main__.py
|
||||
# first dirname call strips of '/__main__.py', second strips off '/pip'
|
||||
# Resulting path is the name of the wheel itself
|
||||
|
|
@ -19,12 +21,12 @@ if __package__ == "":
|
|||
path = os.path.dirname(os.path.dirname(__file__))
|
||||
sys.path.insert(0, path)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
# Work around the error reported in #9540, pending a proper fix.
|
||||
# Note: It is essential the warning filter is set *before* importing
|
||||
# pip, as the deprecation happens at import time, not runtime.
|
||||
warnings.filterwarnings(
|
||||
"ignore", category=DeprecationWarning, module=".*packaging\\.version"
|
||||
'ignore', category=DeprecationWarning, module='.*packaging\\.version',
|
||||
)
|
||||
from pip._internal.cli.main import main as _main
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
from typing import List, Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
import pip._internal.utils.inject_securetransport # noqa
|
||||
from pip._internal.utils import _log
|
||||
|
|
@ -8,7 +11,7 @@ from pip._internal.utils import _log
|
|||
_log.init_logging()
|
||||
|
||||
|
||||
def main(args: (Optional[List[str]]) = None) -> int:
|
||||
def main(args: (list[str] | None) = None) -> int:
|
||||
"""This is preserved for old console scripts that may still be referencing
|
||||
it.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Build Environment used for isolation during sdist building
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
|
|
@ -11,18 +12,27 @@ import zipfile
|
|||
from collections import OrderedDict
|
||||
from sysconfig import get_paths
|
||||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Iterable, Iterator, List, Optional, Set, Tuple, Type
|
||||
|
||||
from pip._vendor.certifi import where
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.version import Version
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pip import __file__ as pip_location
|
||||
from pip._internal.cli.spinners import open_spinner
|
||||
from pip._internal.locations import get_platlib, get_prefixed_libs, get_purelib
|
||||
from pip._internal.locations import get_platlib
|
||||
from pip._internal.locations import get_prefixed_libs
|
||||
from pip._internal.locations import get_purelib
|
||||
from pip._internal.metadata import get_environment
|
||||
from pip._internal.utils.subprocess import call_subprocess
|
||||
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
|
||||
from pip._internal.utils.temp_dir import tempdir_kinds
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._vendor.certifi import where
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
|
|
@ -35,9 +45,9 @@ class _Prefix:
|
|||
self.path = path
|
||||
self.setup = False
|
||||
self.bin_dir = get_paths(
|
||||
"nt" if os.name == "nt" else "posix_prefix",
|
||||
vars={"base": path, "platbase": path},
|
||||
)["scripts"]
|
||||
'nt' if os.name == 'nt' else 'posix_prefix',
|
||||
vars={'base': path, 'platbase': path},
|
||||
)['scripts']
|
||||
self.lib_dirs = get_prefixed_libs(path)
|
||||
|
||||
|
||||
|
|
@ -56,15 +66,14 @@ def _create_standalone_pip() -> Iterator[str]:
|
|||
yield str(source)
|
||||
return
|
||||
|
||||
with TempDirectory(kind="standalone-pip") as tmp_dir:
|
||||
pip_zip = os.path.join(tmp_dir.path, "__env_pip__.zip")
|
||||
with TempDirectory(kind='standalone-pip') as tmp_dir:
|
||||
pip_zip = os.path.join(tmp_dir.path, '__env_pip__.zip')
|
||||
kwargs = {}
|
||||
if sys.version_info >= (3, 8):
|
||||
kwargs["strict_timestamps"] = False
|
||||
with zipfile.ZipFile(pip_zip, "w", **kwargs) as zf:
|
||||
for child in source.rglob("*"):
|
||||
kwargs['strict_timestamps'] = False
|
||||
with zipfile.ZipFile(pip_zip, 'w', **kwargs) as zf:
|
||||
for child in source.rglob('*'):
|
||||
zf.write(child, child.relative_to(source.parent).as_posix())
|
||||
yield os.path.join(pip_zip, "pip")
|
||||
yield os.path.join(pip_zip, 'pip')
|
||||
|
||||
|
||||
class BuildEnvironment:
|
||||
|
|
@ -75,11 +84,11 @@ class BuildEnvironment:
|
|||
|
||||
self._prefixes = OrderedDict(
|
||||
(name, _Prefix(os.path.join(temp_dir.path, name)))
|
||||
for name in ("normal", "overlay")
|
||||
for name in ('normal', 'overlay')
|
||||
)
|
||||
|
||||
self._bin_dirs: List[str] = []
|
||||
self._lib_dirs: List[str] = []
|
||||
self._bin_dirs: list[str] = []
|
||||
self._lib_dirs: list[str] = []
|
||||
for prefix in reversed(list(self._prefixes.values())):
|
||||
self._bin_dirs.append(prefix.bin_dir)
|
||||
self._lib_dirs.extend(prefix.lib_dirs)
|
||||
|
|
@ -90,11 +99,11 @@ class BuildEnvironment:
|
|||
system_sites = {
|
||||
os.path.normcase(site) for site in (get_purelib(), get_platlib())
|
||||
}
|
||||
self._site_dir = os.path.join(temp_dir.path, "site")
|
||||
self._site_dir = os.path.join(temp_dir.path, 'site')
|
||||
if not os.path.exists(self._site_dir):
|
||||
os.mkdir(self._site_dir)
|
||||
with open(
|
||||
os.path.join(self._site_dir, "sitecustomize.py"), "w", encoding="utf-8"
|
||||
os.path.join(self._site_dir, 'sitecustomize.py'), 'w', encoding='utf-8',
|
||||
) as fp:
|
||||
fp.write(
|
||||
textwrap.dedent(
|
||||
|
|
@ -121,18 +130,18 @@ class BuildEnvironment:
|
|||
for path in {lib_dirs!r}:
|
||||
assert not path in sys.path
|
||||
site.addsitedir(path)
|
||||
"""
|
||||
).format(system_sites=system_sites, lib_dirs=self._lib_dirs)
|
||||
""",
|
||||
).format(system_sites=system_sites, lib_dirs=self._lib_dirs),
|
||||
)
|
||||
|
||||
def __enter__(self) -> None:
|
||||
self._save_env = {
|
||||
name: os.environ.get(name, None)
|
||||
for name in ("PATH", "PYTHONNOUSERSITE", "PYTHONPATH")
|
||||
for name in ('PATH', 'PYTHONNOUSERSITE', 'PYTHONPATH')
|
||||
}
|
||||
|
||||
path = self._bin_dirs[:]
|
||||
old_path = self._save_env["PATH"]
|
||||
old_path = self._save_env['PATH']
|
||||
if old_path:
|
||||
path.extend(old_path.split(os.pathsep))
|
||||
|
||||
|
|
@ -140,17 +149,17 @@ class BuildEnvironment:
|
|||
|
||||
os.environ.update(
|
||||
{
|
||||
"PATH": os.pathsep.join(path),
|
||||
"PYTHONNOUSERSITE": "1",
|
||||
"PYTHONPATH": os.pathsep.join(pythonpath),
|
||||
}
|
||||
'PATH': os.pathsep.join(path),
|
||||
'PYTHONNOUSERSITE': '1',
|
||||
'PYTHONPATH': os.pathsep.join(pythonpath),
|
||||
},
|
||||
)
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> None:
|
||||
for varname, old_value in self._save_env.items():
|
||||
if old_value is None:
|
||||
|
|
@ -159,8 +168,8 @@ class BuildEnvironment:
|
|||
os.environ[varname] = old_value
|
||||
|
||||
def check_requirements(
|
||||
self, reqs: Iterable[str]
|
||||
) -> Tuple[Set[Tuple[str, str]], Set[str]]:
|
||||
self, reqs: Iterable[str],
|
||||
) -> tuple[set[tuple[str, str]], set[str]]:
|
||||
"""Return 2 sets:
|
||||
- conflicting requirements: set of (installed, wanted) reqs tuples
|
||||
- missing requirements: set of reqs
|
||||
|
|
@ -176,9 +185,9 @@ class BuildEnvironment:
|
|||
missing.add(req_str)
|
||||
continue
|
||||
if isinstance(dist.version, Version):
|
||||
installed_req_str = f"{req.name}=={dist.version}"
|
||||
installed_req_str = f'{req.name}=={dist.version}'
|
||||
else:
|
||||
installed_req_str = f"{req.name}==={dist.version}"
|
||||
installed_req_str = f'{req.name}==={dist.version}'
|
||||
if dist.version not in req.specifier:
|
||||
conflicting.add((installed_req_str, req_str))
|
||||
# FIXME: Consider direct URL?
|
||||
|
|
@ -186,7 +195,7 @@ class BuildEnvironment:
|
|||
|
||||
def install_requirements(
|
||||
self,
|
||||
finder: "PackageFinder",
|
||||
finder: PackageFinder,
|
||||
requirements: Iterable[str],
|
||||
prefix_as_string: str,
|
||||
*,
|
||||
|
|
@ -210,56 +219,56 @@ class BuildEnvironment:
|
|||
@staticmethod
|
||||
def _install_requirements(
|
||||
pip_runnable: str,
|
||||
finder: "PackageFinder",
|
||||
finder: PackageFinder,
|
||||
requirements: Iterable[str],
|
||||
prefix: _Prefix,
|
||||
*,
|
||||
kind: str,
|
||||
) -> None:
|
||||
args: List[str] = [
|
||||
args: list[str] = [
|
||||
sys.executable,
|
||||
pip_runnable,
|
||||
"install",
|
||||
"--ignore-installed",
|
||||
"--no-user",
|
||||
"--prefix",
|
||||
'install',
|
||||
'--ignore-installed',
|
||||
'--no-user',
|
||||
'--prefix',
|
||||
prefix.path,
|
||||
"--no-warn-script-location",
|
||||
'--no-warn-script-location',
|
||||
]
|
||||
if logger.getEffectiveLevel() <= logging.DEBUG:
|
||||
args.append("-v")
|
||||
for format_control in ("no_binary", "only_binary"):
|
||||
args.append('-v')
|
||||
for format_control in ('no_binary', 'only_binary'):
|
||||
formats = getattr(finder.format_control, format_control)
|
||||
args.extend(
|
||||
(
|
||||
"--" + format_control.replace("_", "-"),
|
||||
",".join(sorted(formats or {":none:"})),
|
||||
)
|
||||
'--' + format_control.replace('_', '-'),
|
||||
','.join(sorted(formats or {':none:'})),
|
||||
),
|
||||
)
|
||||
|
||||
index_urls = finder.index_urls
|
||||
if index_urls:
|
||||
args.extend(["-i", index_urls[0]])
|
||||
args.extend(['-i', index_urls[0]])
|
||||
for extra_index in index_urls[1:]:
|
||||
args.extend(["--extra-index-url", extra_index])
|
||||
args.extend(['--extra-index-url', extra_index])
|
||||
else:
|
||||
args.append("--no-index")
|
||||
args.append('--no-index')
|
||||
for link in finder.find_links:
|
||||
args.extend(["--find-links", link])
|
||||
args.extend(['--find-links', link])
|
||||
|
||||
for host in finder.trusted_hosts:
|
||||
args.extend(["--trusted-host", host])
|
||||
args.extend(['--trusted-host', host])
|
||||
if finder.allow_all_prereleases:
|
||||
args.append("--pre")
|
||||
args.append('--pre')
|
||||
if finder.prefer_binary:
|
||||
args.append("--prefer-binary")
|
||||
args.append("--")
|
||||
args.append('--prefer-binary')
|
||||
args.append('--')
|
||||
args.extend(requirements)
|
||||
extra_environ = {"_PIP_STANDALONE_CERT": where()}
|
||||
with open_spinner(f"Installing {kind}") as spinner:
|
||||
extra_environ = {'_PIP_STANDALONE_CERT': where()}
|
||||
with open_spinner(f'Installing {kind}') as spinner:
|
||||
call_subprocess(
|
||||
args,
|
||||
command_desc=f"pip subprocess to install {kind}",
|
||||
command_desc=f'pip subprocess to install {kind}',
|
||||
spinner=spinner,
|
||||
extra_environ=extra_environ,
|
||||
)
|
||||
|
|
@ -276,9 +285,9 @@ class NoOpBuildEnvironment(BuildEnvironment):
|
|||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
|
@ -287,7 +296,7 @@ class NoOpBuildEnvironment(BuildEnvironment):
|
|||
|
||||
def install_requirements(
|
||||
self,
|
||||
finder: "PackageFinder",
|
||||
finder: PackageFinder,
|
||||
requirements: Iterable[str],
|
||||
prefix_as_string: str,
|
||||
*,
|
||||
|
|
|
|||
|
|
@ -1,29 +1,36 @@
|
|||
"""Cache Management
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
|
||||
from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
|
||||
from pip._internal.exceptions import InvalidWheelFilename
|
||||
from pip._internal.models.format_control import FormatControl
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.models.wheel import Wheel
|
||||
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
|
||||
from pip._internal.utils.temp_dir import tempdir_kinds
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._internal.utils.urls import path_to_url
|
||||
from pip._vendor.packaging.tags import interpreter_name
|
||||
from pip._vendor.packaging.tags import interpreter_version
|
||||
from pip._vendor.packaging.tags import Tag
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _hash_dict(d: Dict[str, str]) -> str:
|
||||
def _hash_dict(d: dict[str, str]) -> str:
|
||||
"""Return a stable sha224 of a dictionary."""
|
||||
s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
|
||||
return hashlib.sha224(s.encode("ascii")).hexdigest()
|
||||
s = json.dumps(d, sort_keys=True, separators=(',', ':'), ensure_ascii=True)
|
||||
return hashlib.sha224(s.encode('ascii')).hexdigest()
|
||||
|
||||
|
||||
class Cache:
|
||||
|
|
@ -38,7 +45,7 @@ class Cache:
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self, cache_dir: str, format_control: FormatControl, allowed_formats: Set[str]
|
||||
self, cache_dir: str, format_control: FormatControl, allowed_formats: set[str],
|
||||
) -> None:
|
||||
super().__init__()
|
||||
assert not cache_dir or os.path.isabs(cache_dir)
|
||||
|
|
@ -46,28 +53,28 @@ class Cache:
|
|||
self.format_control = format_control
|
||||
self.allowed_formats = allowed_formats
|
||||
|
||||
_valid_formats = {"source", "binary"}
|
||||
_valid_formats = {'source', 'binary'}
|
||||
assert self.allowed_formats.union(_valid_formats) == _valid_formats
|
||||
|
||||
def _get_cache_path_parts(self, link: Link) -> List[str]:
|
||||
def _get_cache_path_parts(self, link: Link) -> list[str]:
|
||||
"""Get parts of part that must be os.path.joined with cache_dir"""
|
||||
|
||||
# We want to generate an url to use as our cache key, we don't want to
|
||||
# just re-use the URL because it might have other items in the fragment
|
||||
# and we don't care about those.
|
||||
key_parts = {"url": link.url_without_fragment}
|
||||
key_parts = {'url': link.url_without_fragment}
|
||||
if link.hash_name is not None and link.hash is not None:
|
||||
key_parts[link.hash_name] = link.hash
|
||||
if link.subdirectory_fragment:
|
||||
key_parts["subdirectory"] = link.subdirectory_fragment
|
||||
key_parts['subdirectory'] = link.subdirectory_fragment
|
||||
|
||||
# Include interpreter name, major and minor version in cache key
|
||||
# to cope with ill-behaved sdists that build a different wheel
|
||||
# depending on the python version their setup.py is being run on,
|
||||
# and don't encode the difference in compatibility tags.
|
||||
# https://github.com/pypa/pip/issues/7296
|
||||
key_parts["interpreter_name"] = interpreter_name()
|
||||
key_parts["interpreter_version"] = interpreter_version()
|
||||
key_parts['interpreter_name'] = interpreter_name()
|
||||
key_parts['interpreter_version'] = interpreter_version()
|
||||
|
||||
# Encode our key url with sha224, we'll use this because it has similar
|
||||
# security properties to sha256, but with a shorter total output (and
|
||||
|
|
@ -82,7 +89,7 @@ class Cache:
|
|||
|
||||
return parts
|
||||
|
||||
def _get_candidates(self, link: Link, canonical_package_name: str) -> List[Any]:
|
||||
def _get_candidates(self, link: Link, canonical_package_name: str) -> list[Any]:
|
||||
can_not_cache = not self.cache_dir or not canonical_package_name or not link
|
||||
if can_not_cache:
|
||||
return []
|
||||
|
|
@ -105,8 +112,8 @@ class Cache:
|
|||
def get(
|
||||
self,
|
||||
link: Link,
|
||||
package_name: Optional[str],
|
||||
supported_tags: List[Tag],
|
||||
package_name: str | None,
|
||||
supported_tags: list[Tag],
|
||||
) -> Link:
|
||||
"""Returns a link to a cached item if it exists, otherwise returns the
|
||||
passed link.
|
||||
|
|
@ -118,7 +125,7 @@ class SimpleWheelCache(Cache):
|
|||
"""A cache of wheels for future installs."""
|
||||
|
||||
def __init__(self, cache_dir: str, format_control: FormatControl) -> None:
|
||||
super().__init__(cache_dir, format_control, {"binary"})
|
||||
super().__init__(cache_dir, format_control, {'binary'})
|
||||
|
||||
def get_path_for_link(self, link: Link) -> str:
|
||||
"""Return a directory to store cached wheels for link
|
||||
|
|
@ -138,13 +145,13 @@ class SimpleWheelCache(Cache):
|
|||
parts = self._get_cache_path_parts(link)
|
||||
assert self.cache_dir
|
||||
# Store wheels within the root cache_dir
|
||||
return os.path.join(self.cache_dir, "wheels", *parts)
|
||||
return os.path.join(self.cache_dir, 'wheels', *parts)
|
||||
|
||||
def get(
|
||||
self,
|
||||
link: Link,
|
||||
package_name: Optional[str],
|
||||
supported_tags: List[Tag],
|
||||
package_name: str | None,
|
||||
supported_tags: list[Tag],
|
||||
) -> Link:
|
||||
candidates = []
|
||||
|
||||
|
|
@ -159,8 +166,8 @@ class SimpleWheelCache(Cache):
|
|||
continue
|
||||
if canonicalize_name(wheel.name) != canonical_package_name:
|
||||
logger.debug(
|
||||
"Ignoring cached wheel %s for %s as it "
|
||||
"does not match the expected distribution name %s.",
|
||||
'Ignoring cached wheel %s for %s as it '
|
||||
'does not match the expected distribution name %s.',
|
||||
wheel_name,
|
||||
link,
|
||||
package_name,
|
||||
|
|
@ -174,7 +181,7 @@ class SimpleWheelCache(Cache):
|
|||
wheel.support_index_min(supported_tags),
|
||||
wheel_name,
|
||||
wheel_dir,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
if not candidates:
|
||||
|
|
@ -214,7 +221,7 @@ class WheelCache(Cache):
|
|||
"""
|
||||
|
||||
def __init__(self, cache_dir: str, format_control: FormatControl) -> None:
|
||||
super().__init__(cache_dir, format_control, {"binary"})
|
||||
super().__init__(cache_dir, format_control, {'binary'})
|
||||
self._wheel_cache = SimpleWheelCache(cache_dir, format_control)
|
||||
self._ephem_cache = EphemWheelCache(format_control)
|
||||
|
||||
|
|
@ -227,8 +234,8 @@ class WheelCache(Cache):
|
|||
def get(
|
||||
self,
|
||||
link: Link,
|
||||
package_name: Optional[str],
|
||||
supported_tags: List[Tag],
|
||||
package_name: str | None,
|
||||
supported_tags: list[Tag],
|
||||
) -> Link:
|
||||
cache_entry = self.get_cache_entry(link, package_name, supported_tags)
|
||||
if cache_entry is None:
|
||||
|
|
@ -238,9 +245,9 @@ class WheelCache(Cache):
|
|||
def get_cache_entry(
|
||||
self,
|
||||
link: Link,
|
||||
package_name: Optional[str],
|
||||
supported_tags: List[Tag],
|
||||
) -> Optional[CacheEntry]:
|
||||
package_name: str | None,
|
||||
supported_tags: list[Tag],
|
||||
) -> CacheEntry | None:
|
||||
"""Returns a CacheEntry with a link to a cached item if it exists or
|
||||
None. The cache entry indicates if the item was found in the persistent
|
||||
or ephemeral cache.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Subpackage containing all of pip's command line interface related code
|
||||
"""
|
||||
|
||||
# This file intentionally does not import submodules
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
|
|
@ -1,35 +1,40 @@
|
|||
"""Logic that powers autocompletion installed by ``pip completion``.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
from itertools import chain
|
||||
from typing import Any, Iterable, List, Optional
|
||||
from typing import Any
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.cli.main_parser import create_main_parser
|
||||
from pip._internal.commands import commands_dict, create_command
|
||||
from pip._internal.commands import commands_dict
|
||||
from pip._internal.commands import create_command
|
||||
from pip._internal.metadata import get_default_environment
|
||||
|
||||
|
||||
def autocomplete() -> None:
|
||||
"""Entry Point for completion of main and subcommand options."""
|
||||
# Don't complete if user hasn't sourced bash_completion file.
|
||||
if "PIP_AUTO_COMPLETE" not in os.environ:
|
||||
if 'PIP_AUTO_COMPLETE' not in os.environ:
|
||||
return
|
||||
cwords = os.environ["COMP_WORDS"].split()[1:]
|
||||
cword = int(os.environ["COMP_CWORD"])
|
||||
cwords = os.environ['COMP_WORDS'].split()[1:]
|
||||
cword = int(os.environ['COMP_CWORD'])
|
||||
try:
|
||||
current = cwords[cword - 1]
|
||||
except IndexError:
|
||||
current = ""
|
||||
current = ''
|
||||
|
||||
parser = create_main_parser()
|
||||
subcommands = list(commands_dict)
|
||||
options = []
|
||||
|
||||
# subcommand
|
||||
subcommand_name: Optional[str] = None
|
||||
subcommand_name: str | None = None
|
||||
for word in cwords:
|
||||
if word in subcommands:
|
||||
subcommand_name = word
|
||||
|
|
@ -37,12 +42,12 @@ def autocomplete() -> None:
|
|||
# subcommand options
|
||||
if subcommand_name is not None:
|
||||
# special case: 'help' subcommand has no options
|
||||
if subcommand_name == "help":
|
||||
if subcommand_name == 'help':
|
||||
sys.exit(1)
|
||||
# special case: list locally installed dists for show and uninstall
|
||||
should_list_installed = not current.startswith("-") and subcommand_name in [
|
||||
"show",
|
||||
"uninstall",
|
||||
should_list_installed = not current.startswith('-') and subcommand_name in [
|
||||
'show',
|
||||
'uninstall',
|
||||
]
|
||||
if should_list_installed:
|
||||
env = get_default_environment()
|
||||
|
|
@ -50,8 +55,8 @@ def autocomplete() -> None:
|
|||
installed = [
|
||||
dist.canonical_name
|
||||
for dist in env.iter_installed_distributions(local_only=True)
|
||||
if dist.canonical_name.startswith(lc)
|
||||
and dist.canonical_name not in cwords[1:]
|
||||
if dist.canonical_name.startswith(lc) and
|
||||
dist.canonical_name not in cwords[1:]
|
||||
]
|
||||
# if there are no dists installed, fall back to option completion
|
||||
if installed:
|
||||
|
|
@ -60,10 +65,10 @@ def autocomplete() -> None:
|
|||
sys.exit(1)
|
||||
|
||||
should_list_installables = (
|
||||
not current.startswith("-") and subcommand_name == "install"
|
||||
not current.startswith('-') and subcommand_name == 'install'
|
||||
)
|
||||
if should_list_installables:
|
||||
for path in auto_complete_paths(current, "path"):
|
||||
for path in auto_complete_paths(current, 'path'):
|
||||
print(path)
|
||||
sys.exit(1)
|
||||
|
||||
|
|
@ -75,7 +80,7 @@ def autocomplete() -> None:
|
|||
options.append((opt_str, opt.nargs))
|
||||
|
||||
# filter out previously specified options from available options
|
||||
prev_opts = [x.split("=")[0] for x in cwords[1 : cword - 1]]
|
||||
prev_opts = [x.split('=')[0] for x in cwords[1: cword - 1]]
|
||||
options = [(x, v) for (x, v) in options if x not in prev_opts]
|
||||
# filter options by current input
|
||||
options = [(k, v) for k, v in options if k.startswith(current)]
|
||||
|
|
@ -93,8 +98,8 @@ def autocomplete() -> None:
|
|||
for option in options:
|
||||
opt_label = option[0]
|
||||
# append '=' to options which require args
|
||||
if option[1] and option[0][:2] == "--":
|
||||
opt_label += "="
|
||||
if option[1] and option[0][:2] == '--':
|
||||
opt_label += '='
|
||||
print(opt_label)
|
||||
else:
|
||||
# show main parser options only when necessary
|
||||
|
|
@ -102,7 +107,7 @@ def autocomplete() -> None:
|
|||
opts = [i.option_list for i in parser.option_groups]
|
||||
opts.append(parser.option_list)
|
||||
flattened_opts = chain.from_iterable(opts)
|
||||
if current.startswith("-"):
|
||||
if current.startswith('-'):
|
||||
for opt in flattened_opts:
|
||||
if opt.help != optparse.SUPPRESS_HELP:
|
||||
subcommands += opt._long_opts + opt._short_opts
|
||||
|
|
@ -112,13 +117,13 @@ def autocomplete() -> None:
|
|||
if completion_type:
|
||||
subcommands = list(auto_complete_paths(current, completion_type))
|
||||
|
||||
print(" ".join([x for x in subcommands if x.startswith(current)]))
|
||||
print(' '.join([x for x in subcommands if x.startswith(current)]))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_path_completion_type(
|
||||
cwords: List[str], cword: int, opts: Iterable[Any]
|
||||
) -> Optional[str]:
|
||||
cwords: list[str], cword: int, opts: Iterable[Any],
|
||||
) -> str | None:
|
||||
"""Get the type of path completion (``file``, ``dir``, ``path`` or None)
|
||||
|
||||
:param cwords: same as the environmental variable ``COMP_WORDS``
|
||||
|
|
@ -126,15 +131,15 @@ def get_path_completion_type(
|
|||
:param opts: The available options to check
|
||||
:return: path completion type (``file``, ``dir``, ``path`` or None)
|
||||
"""
|
||||
if cword < 2 or not cwords[cword - 2].startswith("-"):
|
||||
if cword < 2 or not cwords[cword - 2].startswith('-'):
|
||||
return None
|
||||
for opt in opts:
|
||||
if opt.help == optparse.SUPPRESS_HELP:
|
||||
continue
|
||||
for o in str(opt).split("/"):
|
||||
if cwords[cword - 2].split("=")[0] == o:
|
||||
for o in str(opt).split('/'):
|
||||
if cwords[cword - 2].split('=')[0] == o:
|
||||
if not opt.metavar or any(
|
||||
x in ("path", "file", "dir") for x in opt.metavar.split("/")
|
||||
x in ('path', 'file', 'dir') for x in opt.metavar.split('/')
|
||||
):
|
||||
return opt.metavar
|
||||
return None
|
||||
|
|
@ -165,7 +170,7 @@ def auto_complete_paths(current: str, completion_type: str) -> Iterable[str]:
|
|||
# complete regular files when there is not ``<dir>`` after option
|
||||
# complete directories when there is ``<file>``, ``<path>`` or
|
||||
# ``<dir>``after option
|
||||
if completion_type != "dir" and os.path.isfile(opt):
|
||||
if completion_type != 'dir' and os.path.isfile(opt):
|
||||
yield comp_file
|
||||
elif os.path.isdir(opt):
|
||||
yield os.path.join(comp_file, "")
|
||||
yield os.path.join(comp_file, '')
|
||||
|
|
|
|||
|
|
@ -1,49 +1,52 @@
|
|||
"""Base Command class, and related routines"""
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import logging.config
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from optparse import Values
|
||||
from typing import Any, Callable, List, Optional, Tuple
|
||||
|
||||
from pip._vendor.rich import traceback as rich_traceback
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.command_context import CommandContextMixIn
|
||||
from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
|
||||
from pip._internal.cli.status_codes import (
|
||||
ERROR,
|
||||
PREVIOUS_BUILD_DIR_ERROR,
|
||||
UNKNOWN_ERROR,
|
||||
VIRTUALENV_NOT_FOUND,
|
||||
)
|
||||
from pip._internal.exceptions import (
|
||||
BadCommand,
|
||||
CommandError,
|
||||
DiagnosticPipError,
|
||||
InstallationError,
|
||||
NetworkConnectionError,
|
||||
PreviousBuildDirError,
|
||||
UninstallationError,
|
||||
)
|
||||
from pip._internal.cli.parser import ConfigOptionParser
|
||||
from pip._internal.cli.parser import UpdatingDefaultsHelpFormatter
|
||||
from pip._internal.cli.status_codes import ERROR
|
||||
from pip._internal.cli.status_codes import PREVIOUS_BUILD_DIR_ERROR
|
||||
from pip._internal.cli.status_codes import UNKNOWN_ERROR
|
||||
from pip._internal.cli.status_codes import VIRTUALENV_NOT_FOUND
|
||||
from pip._internal.exceptions import BadCommand
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.exceptions import DiagnosticPipError
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import NetworkConnectionError
|
||||
from pip._internal.exceptions import PreviousBuildDirError
|
||||
from pip._internal.exceptions import UninstallationError
|
||||
from pip._internal.utils.filesystem import check_path_owner
|
||||
from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
|
||||
from pip._internal.utils.misc import get_prog, normalize_path
|
||||
from pip._internal.utils.logging import BrokenStdoutLoggingError
|
||||
from pip._internal.utils.logging import setup_logging
|
||||
from pip._internal.utils.misc import get_prog
|
||||
from pip._internal.utils.misc import normalize_path
|
||||
from pip._internal.utils.temp_dir import global_tempdir_manager
|
||||
from pip._internal.utils.temp_dir import tempdir_registry
|
||||
from pip._internal.utils.temp_dir import TempDirectoryTypeRegistry as TempDirRegistry
|
||||
from pip._internal.utils.temp_dir import global_tempdir_manager, tempdir_registry
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
from pip._vendor.rich import traceback as rich_traceback
|
||||
|
||||
__all__ = ["Command"]
|
||||
__all__ = ['Command']
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(CommandContextMixIn):
|
||||
usage: str = ""
|
||||
usage: str = ''
|
||||
ignore_require_venv: bool = False
|
||||
|
||||
def __init__(self, name: str, summary: str, isolated: bool = False) -> None:
|
||||
|
|
@ -53,7 +56,7 @@ class Command(CommandContextMixIn):
|
|||
self.summary = summary
|
||||
self.parser = ConfigOptionParser(
|
||||
usage=self.usage,
|
||||
prog=f"{get_prog()} {name}",
|
||||
prog=f'{get_prog()} {name}',
|
||||
formatter=UpdatingDefaultsHelpFormatter(),
|
||||
add_help_option=False,
|
||||
name=name,
|
||||
|
|
@ -61,10 +64,10 @@ class Command(CommandContextMixIn):
|
|||
isolated=isolated,
|
||||
)
|
||||
|
||||
self.tempdir_registry: Optional[TempDirRegistry] = None
|
||||
self.tempdir_registry: TempDirRegistry | None = None
|
||||
|
||||
# Commands should add options to this option group
|
||||
optgroup_name = f"{self.name.capitalize()} Options"
|
||||
optgroup_name = f'{self.name.capitalize()} Options'
|
||||
self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name)
|
||||
|
||||
# Add the general options
|
||||
|
|
@ -86,23 +89,23 @@ class Command(CommandContextMixIn):
|
|||
"""
|
||||
# Make sure we do the pip version check if the index_group options
|
||||
# are present.
|
||||
assert not hasattr(options, "no_index")
|
||||
assert not hasattr(options, 'no_index')
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
def parse_args(self, args: List[str]) -> Tuple[Values, List[str]]:
|
||||
def parse_args(self, args: list[str]) -> tuple[Values, list[str]]:
|
||||
# factored out for testability
|
||||
return self.parser.parse_args(args)
|
||||
|
||||
def main(self, args: List[str]) -> int:
|
||||
def main(self, args: list[str]) -> int:
|
||||
try:
|
||||
with self.main_context():
|
||||
return self._main(args)
|
||||
finally:
|
||||
logging.shutdown()
|
||||
|
||||
def _main(self, args: List[str]) -> int:
|
||||
def _main(self, args: list[str]) -> int:
|
||||
# We must initialize this before the tempdir manager, otherwise the
|
||||
# configuration would not be accessible by the time we clean up the
|
||||
# tempdir manager.
|
||||
|
|
@ -127,15 +130,15 @@ class Command(CommandContextMixIn):
|
|||
# This also affects isolated builds and it should.
|
||||
|
||||
if options.no_input:
|
||||
os.environ["PIP_NO_INPUT"] = "1"
|
||||
os.environ['PIP_NO_INPUT'] = '1'
|
||||
|
||||
if options.exists_action:
|
||||
os.environ["PIP_EXISTS_ACTION"] = " ".join(options.exists_action)
|
||||
os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action)
|
||||
|
||||
if options.require_venv and not self.ignore_require_venv:
|
||||
# If a venv is required check if it can really be found
|
||||
if not running_under_virtualenv():
|
||||
logger.critical("Could not find an activated virtualenv (required).")
|
||||
logger.critical('Could not find an activated virtualenv (required).')
|
||||
sys.exit(VIRTUALENV_NOT_FOUND)
|
||||
|
||||
if options.cache_dir:
|
||||
|
|
@ -143,23 +146,23 @@ class Command(CommandContextMixIn):
|
|||
if not check_path_owner(options.cache_dir):
|
||||
logger.warning(
|
||||
"The directory '%s' or its parent directory is not owned "
|
||||
"or is not writable by the current user. The cache "
|
||||
"has been disabled. Check the permissions and owner of "
|
||||
"that directory. If executing pip with sudo, you should "
|
||||
'or is not writable by the current user. The cache '
|
||||
'has been disabled. Check the permissions and owner of '
|
||||
'that directory. If executing pip with sudo, you should '
|
||||
"use sudo's -H flag.",
|
||||
options.cache_dir,
|
||||
)
|
||||
options.cache_dir = None
|
||||
|
||||
if "2020-resolver" in options.features_enabled:
|
||||
if '2020-resolver' in options.features_enabled:
|
||||
logger.warning(
|
||||
"--use-feature=2020-resolver no longer has any effect, "
|
||||
"since it is now the default dependency resolver in pip. "
|
||||
"This will become an error in pip 21.0."
|
||||
'--use-feature=2020-resolver no longer has any effect, '
|
||||
'since it is now the default dependency resolver in pip. '
|
||||
'This will become an error in pip 21.0.',
|
||||
)
|
||||
|
||||
def intercepts_unhandled_exc(
|
||||
run_func: Callable[..., int]
|
||||
run_func: Callable[..., int],
|
||||
) -> Callable[..., int]:
|
||||
@functools.wraps(run_func)
|
||||
def exc_logging_wrapper(*args: Any) -> int:
|
||||
|
|
@ -168,13 +171,13 @@ class Command(CommandContextMixIn):
|
|||
assert isinstance(status, int)
|
||||
return status
|
||||
except DiagnosticPipError as exc:
|
||||
logger.error("[present-diagnostic] %s", exc)
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
logger.error('[present-diagnostic] %s', exc)
|
||||
logger.debug('Exception information:', exc_info=True)
|
||||
|
||||
return ERROR
|
||||
except PreviousBuildDirError as exc:
|
||||
logger.critical(str(exc))
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
logger.debug('Exception information:', exc_info=True)
|
||||
|
||||
return PREVIOUS_BUILD_DIR_ERROR
|
||||
except (
|
||||
|
|
@ -184,29 +187,29 @@ class Command(CommandContextMixIn):
|
|||
NetworkConnectionError,
|
||||
) as exc:
|
||||
logger.critical(str(exc))
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
logger.debug('Exception information:', exc_info=True)
|
||||
|
||||
return ERROR
|
||||
except CommandError as exc:
|
||||
logger.critical("%s", exc)
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
logger.critical('%s', exc)
|
||||
logger.debug('Exception information:', exc_info=True)
|
||||
|
||||
return ERROR
|
||||
except BrokenStdoutLoggingError:
|
||||
# Bypass our logger and write any remaining messages to
|
||||
# stderr because stdout no longer works.
|
||||
print("ERROR: Pipe to stdout was broken", file=sys.stderr)
|
||||
print('ERROR: Pipe to stdout was broken', file=sys.stderr)
|
||||
if level_number <= logging.DEBUG:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
return ERROR
|
||||
except KeyboardInterrupt:
|
||||
logger.critical("Operation cancelled by user")
|
||||
logger.debug("Exception information:", exc_info=True)
|
||||
logger.critical('Operation cancelled by user')
|
||||
logger.debug('Exception information:', exc_info=True)
|
||||
|
||||
return ERROR
|
||||
except BaseException:
|
||||
logger.critical("Exception:", exc_info=True)
|
||||
logger.critical('Exception:', exc_info=True)
|
||||
|
||||
return UNKNOWN_ERROR
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,12 @@
|
|||
from contextlib import ExitStack, contextmanager
|
||||
from typing import ContextManager, Iterator, TypeVar
|
||||
from __future__ import annotations
|
||||
|
||||
_T = TypeVar("_T", covariant=True)
|
||||
from contextlib import contextmanager
|
||||
from contextlib import ExitStack
|
||||
from typing import ContextManager
|
||||
from typing import Iterator
|
||||
from typing import TypeVar
|
||||
|
||||
_T = TypeVar('_T', covariant=True)
|
||||
|
||||
|
||||
class CommandContextMixIn:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
"""Primary application entrypoint.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.cli.autocompletion import autocomplete
|
||||
from pip._internal.cli.main_parser import parse_command
|
||||
|
|
@ -42,7 +45,7 @@ logger = logging.getLogger(__name__)
|
|||
# main, this should not be an issue in practice.
|
||||
|
||||
|
||||
def main(args: Optional[List[str]] = None) -> int:
|
||||
def main(args: list[str] | None = None) -> int:
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
|
||||
|
|
@ -54,17 +57,17 @@ def main(args: Optional[List[str]] = None) -> int:
|
|||
try:
|
||||
cmd_name, cmd_args = parse_command(args)
|
||||
except PipError as exc:
|
||||
sys.stderr.write(f"ERROR: {exc}")
|
||||
sys.stderr.write(f'ERROR: {exc}')
|
||||
sys.stderr.write(os.linesep)
|
||||
sys.exit(1)
|
||||
|
||||
# Needed for locale.getpreferredencoding(False) to work
|
||||
# in pip._internal.utils.encoding.auto_decode
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
except locale.Error as e:
|
||||
# setlocale can apparently crash if locale are uninitialized
|
||||
logger.debug("Ignoring error %s when setting locale", e)
|
||||
command = create_command(cmd_name, isolated=("--isolated" in cmd_args))
|
||||
logger.debug('Ignoring error %s when setting locale', e)
|
||||
command = create_command(cmd_name, isolated=('--isolated' in cmd_args))
|
||||
|
||||
return command.main(cmd_args)
|
||||
|
|
|
|||
|
|
@ -1,27 +1,32 @@
|
|||
"""A single place for constructing and exposing the main parser
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import List, Tuple
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
|
||||
from pip._internal.commands import commands_dict, get_similar_commands
|
||||
from pip._internal.cli.parser import ConfigOptionParser
|
||||
from pip._internal.cli.parser import UpdatingDefaultsHelpFormatter
|
||||
from pip._internal.commands import commands_dict
|
||||
from pip._internal.commands import get_similar_commands
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.utils.misc import get_pip_version, get_prog
|
||||
from pip._internal.utils.misc import get_pip_version
|
||||
from pip._internal.utils.misc import get_prog
|
||||
|
||||
__all__ = ["create_main_parser", "parse_command"]
|
||||
__all__ = ['create_main_parser', 'parse_command']
|
||||
|
||||
|
||||
def create_main_parser() -> ConfigOptionParser:
|
||||
"""Creates and returns the main parser for pip's CLI"""
|
||||
|
||||
parser = ConfigOptionParser(
|
||||
usage="\n%prog <command> [options]",
|
||||
usage='\n%prog <command> [options]',
|
||||
add_help_option=False,
|
||||
formatter=UpdatingDefaultsHelpFormatter(),
|
||||
name="global",
|
||||
name='global',
|
||||
prog=get_prog(),
|
||||
)
|
||||
parser.disable_interspersed_args()
|
||||
|
|
@ -36,16 +41,16 @@ def create_main_parser() -> ConfigOptionParser:
|
|||
parser.main = True # type: ignore
|
||||
|
||||
# create command listing for description
|
||||
description = [""] + [
|
||||
f"{name:27} {command_info.summary}"
|
||||
description = [''] + [
|
||||
f'{name:27} {command_info.summary}'
|
||||
for name, command_info in commands_dict.items()
|
||||
]
|
||||
parser.description = "\n".join(description)
|
||||
parser.description = '\n'.join(description)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def parse_command(args: List[str]) -> Tuple[str, List[str]]:
|
||||
def parse_command(args: list[str]) -> tuple[str, list[str]]:
|
||||
parser = create_main_parser()
|
||||
|
||||
# Note: parser calls disable_interspersed_args(), so the result of this
|
||||
|
|
@ -64,7 +69,7 @@ def parse_command(args: List[str]) -> Tuple[str, List[str]]:
|
|||
sys.exit()
|
||||
|
||||
# pip || pip help -> print_help()
|
||||
if not args_else or (args_else[0] == "help" and len(args_else) == 1):
|
||||
if not args_else or (args_else[0] == 'help' and len(args_else) == 1):
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
|
|
@ -78,7 +83,7 @@ def parse_command(args: List[str]) -> Tuple[str, List[str]]:
|
|||
if guess:
|
||||
msg.append(f'maybe you meant "{guess}"')
|
||||
|
||||
raise CommandError(" - ".join(msg))
|
||||
raise CommandError(' - '.join(msg))
|
||||
|
||||
# all the args without the subcommand
|
||||
cmd_args = args[:]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Base option parser setup"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import optparse
|
||||
|
|
@ -6,11 +7,17 @@ import shutil
|
|||
import sys
|
||||
import textwrap
|
||||
from contextlib import suppress
|
||||
from typing import Any, Dict, Iterator, List, Tuple
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.cli.status_codes import UNKNOWN_ERROR
|
||||
from pip._internal.configuration import Configuration, ConfigurationError
|
||||
from pip._internal.utils.misc import redact_auth_from_url, strtobool
|
||||
from pip._internal.configuration import Configuration
|
||||
from pip._internal.configuration import ConfigurationError
|
||||
from pip._internal.utils.misc import redact_auth_from_url
|
||||
from pip._internal.utils.misc import strtobool
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -20,16 +27,16 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
|
|||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
# help position must be aligned with __init__.parseopts.description
|
||||
kwargs["max_help_position"] = 30
|
||||
kwargs["indent_increment"] = 1
|
||||
kwargs["width"] = shutil.get_terminal_size()[0] - 2
|
||||
kwargs['max_help_position'] = 30
|
||||
kwargs['indent_increment'] = 1
|
||||
kwargs['width'] = shutil.get_terminal_size()[0] - 2
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def format_option_strings(self, option: optparse.Option) -> str:
|
||||
return self._format_option_strings(option)
|
||||
|
||||
def _format_option_strings(
|
||||
self, option: optparse.Option, mvarfmt: str = " <{}>", optsep: str = ", "
|
||||
self, option: optparse.Option, mvarfmt: str = ' <{}>', optsep: str = ', ',
|
||||
) -> str:
|
||||
"""
|
||||
Return a comma-separated list of option strings and metavars.
|
||||
|
|
@ -52,49 +59,49 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
|
|||
metavar = option.metavar or option.dest.lower()
|
||||
opts.append(mvarfmt.format(metavar.lower()))
|
||||
|
||||
return "".join(opts)
|
||||
return ''.join(opts)
|
||||
|
||||
def format_heading(self, heading: str) -> str:
|
||||
if heading == "Options":
|
||||
return ""
|
||||
return heading + ":\n"
|
||||
if heading == 'Options':
|
||||
return ''
|
||||
return heading + ':\n'
|
||||
|
||||
def format_usage(self, usage: str) -> str:
|
||||
"""
|
||||
Ensure there is only one newline between usage and the first heading
|
||||
if there is no description.
|
||||
"""
|
||||
msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " "))
|
||||
msg = '\nUsage: {}\n'.format(self.indent_lines(textwrap.dedent(usage), ' '))
|
||||
return msg
|
||||
|
||||
def format_description(self, description: str) -> str:
|
||||
# leave full control over description to us
|
||||
if description:
|
||||
if hasattr(self.parser, "main"):
|
||||
label = "Commands"
|
||||
if hasattr(self.parser, 'main'):
|
||||
label = 'Commands'
|
||||
else:
|
||||
label = "Description"
|
||||
label = 'Description'
|
||||
# some doc strings have initial newlines, some don't
|
||||
description = description.lstrip("\n")
|
||||
description = description.lstrip('\n')
|
||||
# some doc strings have final newlines and spaces, some don't
|
||||
description = description.rstrip()
|
||||
# dedent, then reindent
|
||||
description = self.indent_lines(textwrap.dedent(description), " ")
|
||||
description = f"{label}:\n{description}\n"
|
||||
description = self.indent_lines(textwrap.dedent(description), ' ')
|
||||
description = f'{label}:\n{description}\n'
|
||||
return description
|
||||
else:
|
||||
return ""
|
||||
return ''
|
||||
|
||||
def format_epilog(self, epilog: str) -> str:
|
||||
# leave full control over epilog to us
|
||||
if epilog:
|
||||
return epilog
|
||||
else:
|
||||
return ""
|
||||
return ''
|
||||
|
||||
def indent_lines(self, text: str, indent: str) -> str:
|
||||
new_lines = [indent + line for line in text.split("\n")]
|
||||
return "\n".join(new_lines)
|
||||
new_lines = [indent + line for line in text.split('\n')]
|
||||
return '\n'.join(new_lines)
|
||||
|
||||
|
||||
class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter):
|
||||
|
|
@ -115,7 +122,7 @@ class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter):
|
|||
default_values = self.parser.defaults.get(option.dest)
|
||||
help_text = super().expand_default(option)
|
||||
|
||||
if default_values and option.metavar == "URL":
|
||||
if default_values and option.metavar == 'URL':
|
||||
if isinstance(default_values, str):
|
||||
default_values = [default_values]
|
||||
|
||||
|
|
@ -131,7 +138,7 @@ class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter):
|
|||
|
||||
class CustomOptionParser(optparse.OptionParser):
|
||||
def insert_option_group(
|
||||
self, idx: int, *args: Any, **kwargs: Any
|
||||
self, idx: int, *args: Any, **kwargs: Any,
|
||||
) -> optparse.OptionGroup:
|
||||
"""Insert an OptionGroup at a given position."""
|
||||
group = self.add_option_group(*args, **kwargs)
|
||||
|
|
@ -142,7 +149,7 @@ class CustomOptionParser(optparse.OptionParser):
|
|||
return group
|
||||
|
||||
@property
|
||||
def option_list_all(self) -> List[optparse.Option]:
|
||||
def option_list_all(self) -> list[optparse.Option]:
|
||||
"""Get a list of all options, including those in option groups."""
|
||||
res = self.option_list[:]
|
||||
for i in self.option_groups:
|
||||
|
|
@ -172,15 +179,15 @@ class ConfigOptionParser(CustomOptionParser):
|
|||
try:
|
||||
return option.check_value(key, val)
|
||||
except optparse.OptionValueError as exc:
|
||||
print(f"An error occurred during configuration: {exc}")
|
||||
print(f'An error occurred during configuration: {exc}')
|
||||
sys.exit(3)
|
||||
|
||||
def _get_ordered_configuration_items(self) -> Iterator[Tuple[str, Any]]:
|
||||
def _get_ordered_configuration_items(self) -> Iterator[tuple[str, Any]]:
|
||||
# Configuration gives keys in an unordered manner. Order them.
|
||||
override_order = ["global", self.name, ":env:"]
|
||||
override_order = ['global', self.name, ':env:']
|
||||
|
||||
# Pool the options into different groups
|
||||
section_items: Dict[str, List[Tuple[str, Any]]] = {
|
||||
section_items: dict[str, list[tuple[str, Any]]] = {
|
||||
name: [] for name in override_order
|
||||
}
|
||||
for section_key, val in self.config.items():
|
||||
|
|
@ -192,7 +199,7 @@ class ConfigOptionParser(CustomOptionParser):
|
|||
)
|
||||
continue
|
||||
|
||||
section, key = section_key.split(".", 1)
|
||||
section, key = section_key.split('.', 1)
|
||||
if section in override_order:
|
||||
section_items[section].append((key, val))
|
||||
|
||||
|
|
@ -201,7 +208,7 @@ class ConfigOptionParser(CustomOptionParser):
|
|||
for key, val in section_items[section]:
|
||||
yield key, val
|
||||
|
||||
def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]:
|
||||
def _update_defaults(self, defaults: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Updates the given defaults with values from the config files and
|
||||
the environ. Does a little special handling for certain types of
|
||||
options (lists)."""
|
||||
|
|
@ -212,7 +219,7 @@ class ConfigOptionParser(CustomOptionParser):
|
|||
# Then set the options with those values
|
||||
for key, val in self._get_ordered_configuration_items():
|
||||
# '--' because configuration supports only long names
|
||||
option = self.get_option("--" + key)
|
||||
option = self.get_option('--' + key)
|
||||
|
||||
# Ignore options not present in this parser. E.g. non-globals put
|
||||
# in [global] by users that want them to apply to all applicable
|
||||
|
|
@ -222,31 +229,31 @@ class ConfigOptionParser(CustomOptionParser):
|
|||
|
||||
assert option.dest is not None
|
||||
|
||||
if option.action in ("store_true", "store_false"):
|
||||
if option.action in ('store_true', 'store_false'):
|
||||
try:
|
||||
val = strtobool(val)
|
||||
except ValueError:
|
||||
self.error(
|
||||
"{} is not a valid value for {} option, " # noqa
|
||||
"please specify a boolean value like yes/no, "
|
||||
"true/false or 1/0 instead.".format(val, key)
|
||||
'{} is not a valid value for {} option, ' # noqa
|
||||
'please specify a boolean value like yes/no, '
|
||||
'true/false or 1/0 instead.'.format(val, key),
|
||||
)
|
||||
elif option.action == "count":
|
||||
elif option.action == 'count':
|
||||
with suppress(ValueError):
|
||||
val = strtobool(val)
|
||||
with suppress(ValueError):
|
||||
val = int(val)
|
||||
if not isinstance(val, int) or val < 0:
|
||||
self.error(
|
||||
"{} is not a valid value for {} option, " # noqa
|
||||
"please instead specify either a non-negative integer "
|
||||
"or a boolean value like yes/no or false/true "
|
||||
"which is equivalent to 1/0.".format(val, key)
|
||||
'{} is not a valid value for {} option, ' # noqa
|
||||
'please instead specify either a non-negative integer '
|
||||
'or a boolean value like yes/no or false/true '
|
||||
'which is equivalent to 1/0.'.format(val, key),
|
||||
)
|
||||
elif option.action == "append":
|
||||
elif option.action == 'append':
|
||||
val = val.split()
|
||||
val = [self.check_default(option, key, v) for v in val]
|
||||
elif option.action == "callback":
|
||||
elif option.action == 'callback':
|
||||
assert option.callback is not None
|
||||
late_eval.add(option.dest)
|
||||
opt_str = option.get_opt_string()
|
||||
|
|
@ -289,4 +296,4 @@ class ConfigOptionParser(CustomOptionParser):
|
|||
|
||||
def error(self, msg: str) -> None:
|
||||
self.print_usage(sys.stderr)
|
||||
self.exit(UNKNOWN_ERROR, f"{msg}\n")
|
||||
self.exit(UNKNOWN_ERROR, f'{msg}\n')
|
||||
|
|
|
|||
|
|
@ -1,27 +1,34 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
import sys
|
||||
from signal import SIGINT, default_int_handler, signal
|
||||
from typing import Any, Callable, Iterator, Optional, Tuple
|
||||
|
||||
from pip._vendor.progress.bar import Bar, FillingCirclesBar, IncrementalBar
|
||||
from pip._vendor.progress.spinner import Spinner
|
||||
from pip._vendor.rich.progress import (
|
||||
BarColumn,
|
||||
DownloadColumn,
|
||||
FileSizeColumn,
|
||||
Progress,
|
||||
ProgressColumn,
|
||||
SpinnerColumn,
|
||||
TextColumn,
|
||||
TimeElapsedColumn,
|
||||
TimeRemainingColumn,
|
||||
TransferSpeedColumn,
|
||||
)
|
||||
from signal import default_int_handler
|
||||
from signal import SIGINT
|
||||
from signal import signal
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Iterator
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.logging import get_indentation
|
||||
from pip._internal.utils.misc import format_size
|
||||
from pip._vendor.progress.bar import Bar
|
||||
from pip._vendor.progress.bar import FillingCirclesBar
|
||||
from pip._vendor.progress.bar import IncrementalBar
|
||||
from pip._vendor.progress.spinner import Spinner
|
||||
from pip._vendor.rich.progress import BarColumn
|
||||
from pip._vendor.rich.progress import DownloadColumn
|
||||
from pip._vendor.rich.progress import FileSizeColumn
|
||||
from pip._vendor.rich.progress import Progress
|
||||
from pip._vendor.rich.progress import ProgressColumn
|
||||
from pip._vendor.rich.progress import SpinnerColumn
|
||||
from pip._vendor.rich.progress import TextColumn
|
||||
from pip._vendor.rich.progress import TimeElapsedColumn
|
||||
from pip._vendor.rich.progress import TimeRemainingColumn
|
||||
from pip._vendor.rich.progress import TransferSpeedColumn
|
||||
|
||||
try:
|
||||
from pip._vendor import colorama
|
||||
|
|
@ -34,7 +41,7 @@ DownloadProgressRenderer = Callable[[Iterator[bytes]], Iterator[bytes]]
|
|||
|
||||
|
||||
def _select_progress_class(preferred: Bar, fallback: Bar) -> Bar:
|
||||
encoding = getattr(preferred.file, "encoding", None)
|
||||
encoding = getattr(preferred.file, 'encoding', None)
|
||||
|
||||
# If we don't know what encoding this file is in, then we'll just assume
|
||||
# that it doesn't support unicode and use the ASCII bar.
|
||||
|
|
@ -44,16 +51,16 @@ def _select_progress_class(preferred: Bar, fallback: Bar) -> Bar:
|
|||
# Collect all of the possible characters we want to use with the preferred
|
||||
# bar.
|
||||
characters = [
|
||||
getattr(preferred, "empty_fill", ""),
|
||||
getattr(preferred, "fill", ""),
|
||||
getattr(preferred, 'empty_fill', ''),
|
||||
getattr(preferred, 'fill', ''),
|
||||
]
|
||||
characters += list(getattr(preferred, "phases", []))
|
||||
characters += list(getattr(preferred, 'phases', []))
|
||||
|
||||
# Try to decode the characters we're using for the bar using the encoding
|
||||
# of the given file, if this works then we'll assume that we can use the
|
||||
# fancier bar and if not we'll fall back to the plaintext bar.
|
||||
try:
|
||||
"".join(characters).encode(encoding)
|
||||
''.join(characters).encode(encoding)
|
||||
except UnicodeEncodeError:
|
||||
return fallback
|
||||
else:
|
||||
|
|
@ -126,17 +133,17 @@ class SilentBar(Bar):
|
|||
|
||||
class BlueEmojiBar(IncrementalBar):
|
||||
|
||||
suffix = "%(percent)d%%"
|
||||
bar_prefix = " "
|
||||
bar_suffix = " "
|
||||
phases = ("\U0001F539", "\U0001F537", "\U0001F535")
|
||||
suffix = '%(percent)d%%'
|
||||
bar_prefix = ' '
|
||||
bar_suffix = ' '
|
||||
phases = ('\U0001F539', '\U0001F537', '\U0001F535')
|
||||
|
||||
|
||||
class DownloadProgressMixin:
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
# https://github.com/python/mypy/issues/5887
|
||||
super().__init__(*args, **kwargs) # type: ignore
|
||||
self.message: str = (" " * (get_indentation() + 2)) + self.message
|
||||
self.message: str = (' ' * (get_indentation() + 2)) + self.message
|
||||
|
||||
@property
|
||||
def downloaded(self) -> str:
|
||||
|
|
@ -146,14 +153,14 @@ class DownloadProgressMixin:
|
|||
def download_speed(self) -> str:
|
||||
# Avoid zero division errors...
|
||||
if self.avg == 0.0: # type: ignore
|
||||
return "..."
|
||||
return format_size(1 / self.avg) + "/s" # type: ignore
|
||||
return '...'
|
||||
return format_size(1 / self.avg) + '/s' # type: ignore
|
||||
|
||||
@property
|
||||
def pretty_eta(self) -> str:
|
||||
if self.eta: # type: ignore
|
||||
return f"eta {self.eta_td}" # type: ignore
|
||||
return ""
|
||||
return f'eta {self.eta_td}' # type: ignore
|
||||
return ''
|
||||
|
||||
def iter(self, it): # type: ignore
|
||||
for x in it:
|
||||
|
|
@ -196,8 +203,8 @@ class WindowsMixin:
|
|||
class BaseDownloadProgressBar(WindowsMixin, InterruptibleMixin, DownloadProgressMixin):
|
||||
|
||||
file = sys.stdout
|
||||
message = "%(percent)d%%"
|
||||
suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s"
|
||||
message = '%(percent)d%%'
|
||||
suffix = '%(downloaded)s %(download_speed)s %(pretty_eta)s'
|
||||
|
||||
|
||||
class DefaultDownloadProgressBar(BaseDownloadProgressBar, _BaseBar):
|
||||
|
|
@ -221,14 +228,14 @@ class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, BlueEmojiBar):
|
|||
|
||||
|
||||
class DownloadProgressSpinner(
|
||||
WindowsMixin, InterruptibleMixin, DownloadProgressMixin, Spinner
|
||||
WindowsMixin, InterruptibleMixin, DownloadProgressMixin, Spinner,
|
||||
):
|
||||
|
||||
file = sys.stdout
|
||||
suffix = "%(downloaded)s %(download_speed)s"
|
||||
suffix = '%(downloaded)s %(download_speed)s'
|
||||
|
||||
def next_phase(self) -> str:
|
||||
if not hasattr(self, "_phaser"):
|
||||
if not hasattr(self, '_phaser'):
|
||||
self._phaser = itertools.cycle(self.phases)
|
||||
return next(self._phaser)
|
||||
|
||||
|
|
@ -236,30 +243,30 @@ class DownloadProgressSpinner(
|
|||
message = self.message % self
|
||||
phase = self.next_phase()
|
||||
suffix = self.suffix % self
|
||||
line = "".join(
|
||||
line = ''.join(
|
||||
[
|
||||
message,
|
||||
" " if message else "",
|
||||
' ' if message else '',
|
||||
phase,
|
||||
" " if suffix else "",
|
||||
' ' if suffix else '',
|
||||
suffix,
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
self.writeln(line)
|
||||
|
||||
|
||||
BAR_TYPES = {
|
||||
"off": (DownloadSilentBar, DownloadSilentBar),
|
||||
"on": (DefaultDownloadProgressBar, DownloadProgressSpinner),
|
||||
"ascii": (DownloadBar, DownloadProgressSpinner),
|
||||
"pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner),
|
||||
"emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner),
|
||||
'off': (DownloadSilentBar, DownloadSilentBar),
|
||||
'on': (DefaultDownloadProgressBar, DownloadProgressSpinner),
|
||||
'ascii': (DownloadBar, DownloadProgressSpinner),
|
||||
'pretty': (DownloadFillingCirclesBar, DownloadProgressSpinner),
|
||||
'emoji': (DownloadBlueEmojiProgressBar, DownloadProgressSpinner),
|
||||
}
|
||||
|
||||
|
||||
def _legacy_progress_bar(
|
||||
progress_bar: str, max: Optional[int]
|
||||
progress_bar: str, max: int | None,
|
||||
) -> DownloadProgressRenderer:
|
||||
if max is None or max == 0:
|
||||
return BAR_TYPES[progress_bar][1]().iter # type: ignore
|
||||
|
|
@ -276,13 +283,13 @@ def _rich_progress_bar(
|
|||
bar_type: str,
|
||||
size: int,
|
||||
) -> Iterator[bytes]:
|
||||
assert bar_type == "on", "This should only be used in the default mode."
|
||||
assert bar_type == 'on', 'This should only be used in the default mode.'
|
||||
|
||||
if not size:
|
||||
total = float("inf")
|
||||
columns: Tuple[ProgressColumn, ...] = (
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
SpinnerColumn("line", speed=1.5),
|
||||
total = float('inf')
|
||||
columns: tuple[ProgressColumn, ...] = (
|
||||
TextColumn('[progress.description]{task.description}'),
|
||||
SpinnerColumn('line', speed=1.5),
|
||||
FileSizeColumn(),
|
||||
TransferSpeedColumn(),
|
||||
TimeElapsedColumn(),
|
||||
|
|
@ -290,16 +297,16 @@ def _rich_progress_bar(
|
|||
else:
|
||||
total = size
|
||||
columns = (
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
TextColumn('[progress.description]{task.description}'),
|
||||
BarColumn(),
|
||||
DownloadColumn(),
|
||||
TransferSpeedColumn(),
|
||||
TextColumn("eta"),
|
||||
TextColumn('eta'),
|
||||
TimeRemainingColumn(),
|
||||
)
|
||||
|
||||
progress = Progress(*columns, refresh_per_second=30)
|
||||
task_id = progress.add_task(" " * (get_indentation() + 2), total=total)
|
||||
task_id = progress.add_task(' ' * (get_indentation() + 2), total=total)
|
||||
with progress:
|
||||
for chunk in iterable:
|
||||
yield chunk
|
||||
|
|
@ -307,15 +314,15 @@ def _rich_progress_bar(
|
|||
|
||||
|
||||
def get_download_progress_renderer(
|
||||
*, bar_type: str, size: Optional[int] = None
|
||||
*, bar_type: str, size: int | None = None,
|
||||
) -> DownloadProgressRenderer:
|
||||
"""Get an object that can be used to render the download progress.
|
||||
|
||||
Returns a callable, that takes an iterable to "wrap".
|
||||
"""
|
||||
if bar_type == "on":
|
||||
if bar_type == 'on':
|
||||
return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size)
|
||||
elif bar_type == "off":
|
||||
elif bar_type == 'off':
|
||||
return iter # no-op, when passed an iterator
|
||||
else:
|
||||
return _legacy_progress_bar(bar_type, size)
|
||||
|
|
|
|||
|
|
@ -4,42 +4,43 @@ The classes in this module are in a separate module so the commands not
|
|||
needing download / PackageFinder capability don't unnecessarily import the
|
||||
PackageFinder machinery and all its vendored dependencies, etc.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from functools import partial
|
||||
from optparse import Values
|
||||
from typing import Any, List, Optional, Tuple
|
||||
from typing import Any
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.cache import WheelCache
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.command_context import CommandContextMixIn
|
||||
from pip._internal.exceptions import CommandError, PreviousBuildDirError
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.exceptions import PreviousBuildDirError
|
||||
from pip._internal.index.collector import LinkCollector
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.models.selection_prefs import SelectionPreferences
|
||||
from pip._internal.models.target_python import TargetPython
|
||||
from pip._internal.network.session import PipSession
|
||||
from pip._internal.operations.prepare import RequirementPreparer
|
||||
from pip._internal.req.constructors import (
|
||||
install_req_from_editable,
|
||||
install_req_from_line,
|
||||
install_req_from_parsed_requirement,
|
||||
install_req_from_req_string,
|
||||
)
|
||||
from pip._internal.req.constructors import install_req_from_editable
|
||||
from pip._internal.req.constructors import install_req_from_line
|
||||
from pip._internal.req.constructors import install_req_from_parsed_requirement
|
||||
from pip._internal.req.constructors import install_req_from_req_string
|
||||
from pip._internal.req.req_file import parse_requirements
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.req.req_tracker import RequirementTracker
|
||||
from pip._internal.resolution.base import BaseResolver
|
||||
from pip._internal.self_outdated_check import pip_self_version_check
|
||||
from pip._internal.utils.deprecation import deprecated
|
||||
from pip._internal.utils.temp_dir import (
|
||||
TempDirectory,
|
||||
TempDirectoryTypeRegistry,
|
||||
tempdir_kinds,
|
||||
)
|
||||
from pip._internal.utils.temp_dir import tempdir_kinds
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._internal.utils.temp_dir import TempDirectoryTypeRegistry
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -53,17 +54,17 @@ class SessionCommandMixin(CommandContextMixIn):
|
|||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._session: Optional[PipSession] = None
|
||||
self._session: PipSession | None = None
|
||||
|
||||
@classmethod
|
||||
def _get_index_urls(cls, options: Values) -> Optional[List[str]]:
|
||||
def _get_index_urls(cls, options: Values) -> list[str] | None:
|
||||
"""Return a list of index urls from user-provided options."""
|
||||
index_urls = []
|
||||
if not getattr(options, "no_index", False):
|
||||
url = getattr(options, "index_url", None)
|
||||
if not getattr(options, 'no_index', False):
|
||||
url = getattr(options, 'index_url', None)
|
||||
if url:
|
||||
index_urls.append(url)
|
||||
urls = getattr(options, "extra_index_urls", None)
|
||||
urls = getattr(options, 'extra_index_urls', None)
|
||||
if urls:
|
||||
index_urls.extend(urls)
|
||||
# Return None rather than an empty list
|
||||
|
|
@ -82,13 +83,13 @@ class SessionCommandMixin(CommandContextMixIn):
|
|||
def _build_session(
|
||||
self,
|
||||
options: Values,
|
||||
retries: Optional[int] = None,
|
||||
timeout: Optional[int] = None,
|
||||
retries: int | None = None,
|
||||
timeout: int | None = None,
|
||||
) -> PipSession:
|
||||
assert not options.cache_dir or os.path.isabs(options.cache_dir)
|
||||
session = PipSession(
|
||||
cache=(
|
||||
os.path.join(options.cache_dir, "http") if options.cache_dir else None
|
||||
os.path.join(options.cache_dir, 'http') if options.cache_dir else None
|
||||
),
|
||||
retries=retries if retries is not None else options.retries,
|
||||
trusted_hosts=options.trusted_hosts,
|
||||
|
|
@ -110,8 +111,8 @@ class SessionCommandMixin(CommandContextMixIn):
|
|||
# Handle configured proxies
|
||||
if options.proxy:
|
||||
session.proxies = {
|
||||
"http": options.proxy,
|
||||
"https": options.proxy,
|
||||
'http': options.proxy,
|
||||
'https': options.proxy,
|
||||
}
|
||||
|
||||
# Determine if we can prompt the user for authentication or not
|
||||
|
|
@ -135,14 +136,14 @@ class IndexGroupCommand(Command, SessionCommandMixin):
|
|||
This overrides the default behavior of not doing the check.
|
||||
"""
|
||||
# Make sure the index_group options are present.
|
||||
assert hasattr(options, "no_index")
|
||||
assert hasattr(options, 'no_index')
|
||||
|
||||
if options.disable_pip_version_check or options.no_index:
|
||||
return
|
||||
|
||||
# Otherwise, check if we're using the latest version of pip available.
|
||||
session = self._build_session(
|
||||
options, retries=0, timeout=min(5, options.timeout)
|
||||
options, retries=0, timeout=min(5, options.timeout),
|
||||
)
|
||||
with session:
|
||||
pip_self_version_check(session, options)
|
||||
|
|
@ -164,14 +165,14 @@ def warn_if_run_as_root() -> None:
|
|||
"""
|
||||
if running_under_virtualenv():
|
||||
return
|
||||
if not hasattr(os, "getuid"):
|
||||
if not hasattr(os, 'getuid'):
|
||||
return
|
||||
# On Windows, there are no "system managed" Python packages. Installing as
|
||||
# Administrator via pip is the correct way of updating system environments.
|
||||
#
|
||||
# We choose sys.platform over utils.compat.WINDOWS here to enable Mypy platform
|
||||
# checks: https://mypy.readthedocs.io/en/stable/common_issues.html
|
||||
if sys.platform == "win32" or sys.platform == "cygwin":
|
||||
if sys.platform == 'win32' or sys.platform == 'cygwin':
|
||||
return
|
||||
|
||||
if os.getuid() != 0:
|
||||
|
|
@ -179,9 +180,9 @@ def warn_if_run_as_root() -> None:
|
|||
|
||||
logger.warning(
|
||||
"Running pip as the 'root' user can result in broken permissions and "
|
||||
"conflicting behaviour with the system package manager. "
|
||||
"It is recommended to use a virtual environment instead: "
|
||||
"https://pip.pypa.io/warnings/venv"
|
||||
'conflicting behaviour with the system package manager. '
|
||||
'It is recommended to use a virtual environment instead: '
|
||||
'https://pip.pypa.io/warnings/venv',
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -195,8 +196,8 @@ def with_cleanup(func: Any) -> Any:
|
|||
registry.set_delete(t, False)
|
||||
|
||||
def wrapper(
|
||||
self: RequirementCommand, options: Values, args: List[Any]
|
||||
) -> Optional[int]:
|
||||
self: RequirementCommand, options: Values, args: list[Any],
|
||||
) -> int | None:
|
||||
assert self.tempdir_registry is not None
|
||||
if options.no_clean:
|
||||
configure_tempdir_registry(self.tempdir_registry)
|
||||
|
|
@ -222,30 +223,30 @@ class RequirementCommand(IndexGroupCommand):
|
|||
@staticmethod
|
||||
def determine_resolver_variant(options: Values) -> str:
|
||||
"""Determines which resolver should be used, based on the given options."""
|
||||
if "legacy-resolver" in options.deprecated_features_enabled:
|
||||
return "legacy"
|
||||
if 'legacy-resolver' in options.deprecated_features_enabled:
|
||||
return 'legacy'
|
||||
|
||||
return "2020-resolver"
|
||||
return '2020-resolver'
|
||||
|
||||
@staticmethod
|
||||
def determine_build_failure_suppression(options: Values) -> bool:
|
||||
"""Determines whether build failures should be suppressed and backtracked on."""
|
||||
if "backtrack-on-build-failures" not in options.deprecated_features_enabled:
|
||||
if 'backtrack-on-build-failures' not in options.deprecated_features_enabled:
|
||||
return False
|
||||
|
||||
if "legacy-resolver" in options.deprecated_features_enabled:
|
||||
raise CommandError("Cannot backtrack with legacy resolver.")
|
||||
if 'legacy-resolver' in options.deprecated_features_enabled:
|
||||
raise CommandError('Cannot backtrack with legacy resolver.')
|
||||
|
||||
deprecated(
|
||||
reason=(
|
||||
"Backtracking on build failures can mask issues related to how "
|
||||
"a package generates metadata or builds a wheel. This flag will "
|
||||
"be removed in pip 22.2."
|
||||
'Backtracking on build failures can mask issues related to how '
|
||||
'a package generates metadata or builds a wheel. This flag will '
|
||||
'be removed in pip 22.2.'
|
||||
),
|
||||
gone_in=None,
|
||||
replacement=(
|
||||
"avoiding known-bad versions by explicitly telling pip to ignore them "
|
||||
"(either directly as requirements, or via a constraints file)"
|
||||
'avoiding known-bad versions by explicitly telling pip to ignore them '
|
||||
'(either directly as requirements, or via a constraints file)'
|
||||
),
|
||||
feature_flag=None,
|
||||
issue=10655,
|
||||
|
|
@ -261,7 +262,7 @@ class RequirementCommand(IndexGroupCommand):
|
|||
session: PipSession,
|
||||
finder: PackageFinder,
|
||||
use_user_site: bool,
|
||||
download_dir: Optional[str] = None,
|
||||
download_dir: str | None = None,
|
||||
verbosity: int = 0,
|
||||
) -> RequirementPreparer:
|
||||
"""
|
||||
|
|
@ -271,42 +272,42 @@ class RequirementCommand(IndexGroupCommand):
|
|||
assert temp_build_dir_path is not None
|
||||
|
||||
resolver_variant = cls.determine_resolver_variant(options)
|
||||
if resolver_variant == "2020-resolver":
|
||||
lazy_wheel = "fast-deps" in options.features_enabled
|
||||
if resolver_variant == '2020-resolver':
|
||||
lazy_wheel = 'fast-deps' in options.features_enabled
|
||||
if lazy_wheel:
|
||||
logger.warning(
|
||||
"pip is using lazily downloaded wheels using HTTP "
|
||||
"range requests to obtain dependency information. "
|
||||
"This experimental feature is enabled through "
|
||||
"--use-feature=fast-deps and it is not ready for "
|
||||
"production."
|
||||
'pip is using lazily downloaded wheels using HTTP '
|
||||
'range requests to obtain dependency information. '
|
||||
'This experimental feature is enabled through '
|
||||
'--use-feature=fast-deps and it is not ready for '
|
||||
'production.',
|
||||
)
|
||||
else:
|
||||
lazy_wheel = False
|
||||
if "fast-deps" in options.features_enabled:
|
||||
if 'fast-deps' in options.features_enabled:
|
||||
logger.warning(
|
||||
"fast-deps has no effect when used with the legacy resolver."
|
||||
'fast-deps has no effect when used with the legacy resolver.',
|
||||
)
|
||||
|
||||
in_tree_build = "out-of-tree-build" not in options.deprecated_features_enabled
|
||||
if "in-tree-build" in options.features_enabled:
|
||||
in_tree_build = 'out-of-tree-build' not in options.deprecated_features_enabled
|
||||
if 'in-tree-build' in options.features_enabled:
|
||||
deprecated(
|
||||
reason="In-tree builds are now the default.",
|
||||
replacement="to remove the --use-feature=in-tree-build flag",
|
||||
gone_in="22.1",
|
||||
reason='In-tree builds are now the default.',
|
||||
replacement='to remove the --use-feature=in-tree-build flag',
|
||||
gone_in='22.1',
|
||||
)
|
||||
if "out-of-tree-build" in options.deprecated_features_enabled:
|
||||
if 'out-of-tree-build' in options.deprecated_features_enabled:
|
||||
deprecated(
|
||||
reason="Out-of-tree builds are deprecated.",
|
||||
reason='Out-of-tree builds are deprecated.',
|
||||
replacement=None,
|
||||
gone_in="22.1",
|
||||
gone_in='22.1',
|
||||
)
|
||||
|
||||
if options.progress_bar not in {"on", "off"}:
|
||||
if options.progress_bar not in {'on', 'off'}:
|
||||
deprecated(
|
||||
reason="Custom progress bar styles are deprecated",
|
||||
replacement="to use the default progress bar style.",
|
||||
gone_in="22.1",
|
||||
reason='Custom progress bar styles are deprecated',
|
||||
replacement='to use the default progress bar style.',
|
||||
gone_in='22.1',
|
||||
)
|
||||
|
||||
return RequirementPreparer(
|
||||
|
|
@ -331,14 +332,14 @@ class RequirementCommand(IndexGroupCommand):
|
|||
preparer: RequirementPreparer,
|
||||
finder: PackageFinder,
|
||||
options: Values,
|
||||
wheel_cache: Optional[WheelCache] = None,
|
||||
wheel_cache: WheelCache | None = None,
|
||||
use_user_site: bool = False,
|
||||
ignore_installed: bool = True,
|
||||
ignore_requires_python: bool = False,
|
||||
force_reinstall: bool = False,
|
||||
upgrade_strategy: str = "to-satisfy-only",
|
||||
use_pep517: Optional[bool] = None,
|
||||
py_version_info: Optional[Tuple[int, ...]] = None,
|
||||
upgrade_strategy: str = 'to-satisfy-only',
|
||||
use_pep517: bool | None = None,
|
||||
py_version_info: tuple[int, ...] | None = None,
|
||||
) -> BaseResolver:
|
||||
"""
|
||||
Create a Resolver instance for the given parameters.
|
||||
|
|
@ -353,7 +354,7 @@ class RequirementCommand(IndexGroupCommand):
|
|||
# The long import name and duplicated invocation is needed to convince
|
||||
# Mypy into correctly typechecking. Otherwise it would complain the
|
||||
# "Resolver" class being redefined.
|
||||
if resolver_variant == "2020-resolver":
|
||||
if resolver_variant == '2020-resolver':
|
||||
import pip._internal.resolution.resolvelib.resolver
|
||||
|
||||
return pip._internal.resolution.resolvelib.resolver.Resolver(
|
||||
|
|
@ -388,15 +389,15 @@ class RequirementCommand(IndexGroupCommand):
|
|||
|
||||
def get_requirements(
|
||||
self,
|
||||
args: List[str],
|
||||
args: list[str],
|
||||
options: Values,
|
||||
finder: PackageFinder,
|
||||
session: PipSession,
|
||||
) -> List[InstallRequirement]:
|
||||
) -> list[InstallRequirement]:
|
||||
"""
|
||||
Parse command-line arguments into the corresponding requirements.
|
||||
"""
|
||||
requirements: List[InstallRequirement] = []
|
||||
requirements: list[InstallRequirement] = []
|
||||
for filename in options.constraints:
|
||||
for parsed_req in parse_requirements(
|
||||
filename,
|
||||
|
|
@ -434,7 +435,7 @@ class RequirementCommand(IndexGroupCommand):
|
|||
# NOTE: options.require_hashes may be set if --require-hashes is True
|
||||
for filename in options.requirements:
|
||||
for parsed_req in parse_requirements(
|
||||
filename, finder=finder, options=options, session=session
|
||||
filename, finder=finder, options=options, session=session,
|
||||
):
|
||||
req_to_add = install_req_from_parsed_requirement(
|
||||
parsed_req,
|
||||
|
|
@ -449,18 +450,18 @@ class RequirementCommand(IndexGroupCommand):
|
|||
options.require_hashes = True
|
||||
|
||||
if not (args or options.editables or options.requirements):
|
||||
opts = {"name": self.name}
|
||||
opts = {'name': self.name}
|
||||
if options.find_links:
|
||||
raise CommandError(
|
||||
"You must give at least one requirement to {name} "
|
||||
'You must give at least one requirement to {name} '
|
||||
'(maybe you meant "pip {name} {links}"?)'.format(
|
||||
**dict(opts, links=" ".join(options.find_links))
|
||||
)
|
||||
**dict(opts, links=' '.join(options.find_links)),
|
||||
),
|
||||
)
|
||||
else:
|
||||
raise CommandError(
|
||||
"You must give at least one requirement to {name} "
|
||||
'(see "pip help {name}")'.format(**opts)
|
||||
'You must give at least one requirement to {name} '
|
||||
'(see "pip help {name}")'.format(**opts),
|
||||
)
|
||||
|
||||
return requirements
|
||||
|
|
@ -480,8 +481,8 @@ class RequirementCommand(IndexGroupCommand):
|
|||
self,
|
||||
options: Values,
|
||||
session: PipSession,
|
||||
target_python: Optional[TargetPython] = None,
|
||||
ignore_requires_python: Optional[bool] = None,
|
||||
target_python: TargetPython | None = None,
|
||||
ignore_requires_python: bool | None = None,
|
||||
) -> PackageFinder:
|
||||
"""
|
||||
Create a package finder appropriate to this requirement command.
|
||||
|
|
@ -502,5 +503,5 @@ class RequirementCommand(IndexGroupCommand):
|
|||
link_collector=link_collector,
|
||||
selection_prefs=selection_prefs,
|
||||
target_python=target_python,
|
||||
use_deprecated_html5lib="html5lib" in options.deprecated_features_enabled,
|
||||
use_deprecated_html5lib='html5lib' in options.deprecated_features_enabled,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import itertools
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from typing import IO, Iterator
|
||||
|
||||
from pip._vendor.progress import HIDE_CURSOR, SHOW_CURSOR
|
||||
from typing import IO
|
||||
from typing import Iterator
|
||||
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.logging import get_indentation
|
||||
from pip._vendor.progress import HIDE_CURSOR
|
||||
from pip._vendor.progress import SHOW_CURSOR
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -26,7 +29,7 @@ class InteractiveSpinner(SpinnerInterface):
|
|||
self,
|
||||
message: str,
|
||||
file: IO[str] = None,
|
||||
spin_chars: str = "-\\|/",
|
||||
spin_chars: str = '-\\|/',
|
||||
# Empirically, 8 updates/second looks nice
|
||||
min_update_interval_seconds: float = 0.125,
|
||||
):
|
||||
|
|
@ -39,15 +42,15 @@ class InteractiveSpinner(SpinnerInterface):
|
|||
|
||||
self._spin_cycle = itertools.cycle(spin_chars)
|
||||
|
||||
self._file.write(" " * get_indentation() + self._message + " ... ")
|
||||
self._file.write(' ' * get_indentation() + self._message + ' ... ')
|
||||
self._width = 0
|
||||
|
||||
def _write(self, status: str) -> None:
|
||||
assert not self._finished
|
||||
# Erase what we wrote before by backspacing to the beginning, writing
|
||||
# spaces to overwrite the old text, and then backspacing again
|
||||
backup = "\b" * self._width
|
||||
self._file.write(backup + " " * self._width + backup)
|
||||
backup = '\b' * self._width
|
||||
self._file.write(backup + ' ' * self._width + backup)
|
||||
# Now we have a blank slate to add our status
|
||||
self._file.write(status)
|
||||
self._width = len(status)
|
||||
|
|
@ -65,7 +68,7 @@ class InteractiveSpinner(SpinnerInterface):
|
|||
if self._finished:
|
||||
return
|
||||
self._write(final_status)
|
||||
self._file.write("\n")
|
||||
self._file.write('\n')
|
||||
self._file.flush()
|
||||
self._finished = True
|
||||
|
||||
|
|
@ -79,19 +82,19 @@ class NonInteractiveSpinner(SpinnerInterface):
|
|||
self._message = message
|
||||
self._finished = False
|
||||
self._rate_limiter = RateLimiter(min_update_interval_seconds)
|
||||
self._update("started")
|
||||
self._update('started')
|
||||
|
||||
def _update(self, status: str) -> None:
|
||||
assert not self._finished
|
||||
self._rate_limiter.reset()
|
||||
logger.info("%s: %s", self._message, status)
|
||||
logger.info('%s: %s', self._message, status)
|
||||
|
||||
def spin(self) -> None:
|
||||
if self._finished:
|
||||
return
|
||||
if not self._rate_limiter.ready():
|
||||
return
|
||||
self._update("still running...")
|
||||
self._update('still running...')
|
||||
|
||||
def finish(self, final_status: str) -> None:
|
||||
if self._finished:
|
||||
|
|
@ -129,13 +132,13 @@ def open_spinner(message: str) -> Iterator[SpinnerInterface]:
|
|||
with hidden_cursor(sys.stdout):
|
||||
yield spinner
|
||||
except KeyboardInterrupt:
|
||||
spinner.finish("canceled")
|
||||
spinner.finish('canceled')
|
||||
raise
|
||||
except Exception:
|
||||
spinner.finish("error")
|
||||
spinner.finish('error')
|
||||
raise
|
||||
else:
|
||||
spinner.finish("done")
|
||||
spinner.finish('done')
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from __future__ import annotations
|
||||
SUCCESS = 0
|
||||
ERROR = 1
|
||||
UNKNOWN_ERROR = 2
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
"""
|
||||
Package containing all pip commands
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
from collections import namedtuple
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
|
||||
CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary")
|
||||
CommandInfo = namedtuple('CommandInfo', 'module_path, class_name, summary')
|
||||
|
||||
# This dictionary does a bunch of heavy lifting for help output:
|
||||
# - Enables avoiding additional (costly) imports for presenting `--help`.
|
||||
|
|
@ -17,86 +20,86 @@ CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary")
|
|||
# Even though the module path starts with the same "pip._internal.commands"
|
||||
# prefix, the full path makes testing easier (specifically when modifying
|
||||
# `commands_dict` in test setup / teardown).
|
||||
commands_dict: Dict[str, CommandInfo] = {
|
||||
"install": CommandInfo(
|
||||
"pip._internal.commands.install",
|
||||
"InstallCommand",
|
||||
"Install packages.",
|
||||
commands_dict: dict[str, CommandInfo] = {
|
||||
'install': CommandInfo(
|
||||
'pip._internal.commands.install',
|
||||
'InstallCommand',
|
||||
'Install packages.',
|
||||
),
|
||||
"download": CommandInfo(
|
||||
"pip._internal.commands.download",
|
||||
"DownloadCommand",
|
||||
"Download packages.",
|
||||
'download': CommandInfo(
|
||||
'pip._internal.commands.download',
|
||||
'DownloadCommand',
|
||||
'Download packages.',
|
||||
),
|
||||
"uninstall": CommandInfo(
|
||||
"pip._internal.commands.uninstall",
|
||||
"UninstallCommand",
|
||||
"Uninstall packages.",
|
||||
'uninstall': CommandInfo(
|
||||
'pip._internal.commands.uninstall',
|
||||
'UninstallCommand',
|
||||
'Uninstall packages.',
|
||||
),
|
||||
"freeze": CommandInfo(
|
||||
"pip._internal.commands.freeze",
|
||||
"FreezeCommand",
|
||||
"Output installed packages in requirements format.",
|
||||
'freeze': CommandInfo(
|
||||
'pip._internal.commands.freeze',
|
||||
'FreezeCommand',
|
||||
'Output installed packages in requirements format.',
|
||||
),
|
||||
"list": CommandInfo(
|
||||
"pip._internal.commands.list",
|
||||
"ListCommand",
|
||||
"List installed packages.",
|
||||
'list': CommandInfo(
|
||||
'pip._internal.commands.list',
|
||||
'ListCommand',
|
||||
'List installed packages.',
|
||||
),
|
||||
"show": CommandInfo(
|
||||
"pip._internal.commands.show",
|
||||
"ShowCommand",
|
||||
"Show information about installed packages.",
|
||||
'show': CommandInfo(
|
||||
'pip._internal.commands.show',
|
||||
'ShowCommand',
|
||||
'Show information about installed packages.',
|
||||
),
|
||||
"check": CommandInfo(
|
||||
"pip._internal.commands.check",
|
||||
"CheckCommand",
|
||||
"Verify installed packages have compatible dependencies.",
|
||||
'check': CommandInfo(
|
||||
'pip._internal.commands.check',
|
||||
'CheckCommand',
|
||||
'Verify installed packages have compatible dependencies.',
|
||||
),
|
||||
"config": CommandInfo(
|
||||
"pip._internal.commands.configuration",
|
||||
"ConfigurationCommand",
|
||||
"Manage local and global configuration.",
|
||||
'config': CommandInfo(
|
||||
'pip._internal.commands.configuration',
|
||||
'ConfigurationCommand',
|
||||
'Manage local and global configuration.',
|
||||
),
|
||||
"search": CommandInfo(
|
||||
"pip._internal.commands.search",
|
||||
"SearchCommand",
|
||||
"Search PyPI for packages.",
|
||||
'search': CommandInfo(
|
||||
'pip._internal.commands.search',
|
||||
'SearchCommand',
|
||||
'Search PyPI for packages.',
|
||||
),
|
||||
"cache": CommandInfo(
|
||||
"pip._internal.commands.cache",
|
||||
"CacheCommand",
|
||||
'cache': CommandInfo(
|
||||
'pip._internal.commands.cache',
|
||||
'CacheCommand',
|
||||
"Inspect and manage pip's wheel cache.",
|
||||
),
|
||||
"index": CommandInfo(
|
||||
"pip._internal.commands.index",
|
||||
"IndexCommand",
|
||||
"Inspect information available from package indexes.",
|
||||
'index': CommandInfo(
|
||||
'pip._internal.commands.index',
|
||||
'IndexCommand',
|
||||
'Inspect information available from package indexes.',
|
||||
),
|
||||
"wheel": CommandInfo(
|
||||
"pip._internal.commands.wheel",
|
||||
"WheelCommand",
|
||||
"Build wheels from your requirements.",
|
||||
'wheel': CommandInfo(
|
||||
'pip._internal.commands.wheel',
|
||||
'WheelCommand',
|
||||
'Build wheels from your requirements.',
|
||||
),
|
||||
"hash": CommandInfo(
|
||||
"pip._internal.commands.hash",
|
||||
"HashCommand",
|
||||
"Compute hashes of package archives.",
|
||||
'hash': CommandInfo(
|
||||
'pip._internal.commands.hash',
|
||||
'HashCommand',
|
||||
'Compute hashes of package archives.',
|
||||
),
|
||||
"completion": CommandInfo(
|
||||
"pip._internal.commands.completion",
|
||||
"CompletionCommand",
|
||||
"A helper command used for command completion.",
|
||||
'completion': CommandInfo(
|
||||
'pip._internal.commands.completion',
|
||||
'CompletionCommand',
|
||||
'A helper command used for command completion.',
|
||||
),
|
||||
"debug": CommandInfo(
|
||||
"pip._internal.commands.debug",
|
||||
"DebugCommand",
|
||||
"Show information useful for debugging.",
|
||||
'debug': CommandInfo(
|
||||
'pip._internal.commands.debug',
|
||||
'DebugCommand',
|
||||
'Show information useful for debugging.',
|
||||
),
|
||||
"help": CommandInfo(
|
||||
"pip._internal.commands.help",
|
||||
"HelpCommand",
|
||||
"Show help for commands.",
|
||||
'help': CommandInfo(
|
||||
'pip._internal.commands.help',
|
||||
'HelpCommand',
|
||||
'Show help for commands.',
|
||||
),
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +116,7 @@ def create_command(name: str, **kwargs: Any) -> Command:
|
|||
return command
|
||||
|
||||
|
||||
def get_similar_commands(name: str) -> Optional[str]:
|
||||
def get_similar_commands(name: str) -> str | None:
|
||||
"""Command name auto-correct."""
|
||||
from difflib import get_close_matches
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,17 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import textwrap
|
||||
from optparse import Values
|
||||
from typing import Any, List
|
||||
from typing import Any
|
||||
from typing import List
|
||||
|
||||
import pip._internal.utils.filesystem as filesystem
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
from pip._internal.exceptions import CommandError, PipError
|
||||
from pip._internal.cli.status_codes import ERROR
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.exceptions import PipError
|
||||
from pip._internal.utils.logging import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
|
@ -39,34 +44,34 @@ class CacheCommand(Command):
|
|||
def add_options(self) -> None:
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--format",
|
||||
action="store",
|
||||
dest="list_format",
|
||||
default="human",
|
||||
choices=("human", "abspath"),
|
||||
help="Select the output format among: human (default) or abspath",
|
||||
'--format',
|
||||
action='store',
|
||||
dest='list_format',
|
||||
default='human',
|
||||
choices=('human', 'abspath'),
|
||||
help='Select the output format among: human (default) or abspath',
|
||||
)
|
||||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
handlers = {
|
||||
"dir": self.get_cache_dir,
|
||||
"info": self.get_cache_info,
|
||||
"list": self.list_cache_items,
|
||||
"remove": self.remove_cache_items,
|
||||
"purge": self.purge_cache,
|
||||
'dir': self.get_cache_dir,
|
||||
'info': self.get_cache_info,
|
||||
'list': self.list_cache_items,
|
||||
'remove': self.remove_cache_items,
|
||||
'purge': self.purge_cache,
|
||||
}
|
||||
|
||||
if not options.cache_dir:
|
||||
logger.error("pip cache commands can not function since cache is disabled.")
|
||||
logger.error('pip cache commands can not function since cache is disabled.')
|
||||
return ERROR
|
||||
|
||||
# Determine action
|
||||
if not args or args[0] not in handlers:
|
||||
logger.error(
|
||||
"Need an action (%s) to perform.",
|
||||
", ".join(sorted(handlers)),
|
||||
'Need an action (%s) to perform.',
|
||||
', '.join(sorted(handlers)),
|
||||
)
|
||||
return ERROR
|
||||
|
||||
|
|
@ -81,21 +86,21 @@ class CacheCommand(Command):
|
|||
|
||||
return SUCCESS
|
||||
|
||||
def get_cache_dir(self, options: Values, args: List[Any]) -> None:
|
||||
def get_cache_dir(self, options: Values, args: list[Any]) -> None:
|
||||
if args:
|
||||
raise CommandError("Too many arguments")
|
||||
raise CommandError('Too many arguments')
|
||||
|
||||
logger.info(options.cache_dir)
|
||||
|
||||
def get_cache_info(self, options: Values, args: List[Any]) -> None:
|
||||
def get_cache_info(self, options: Values, args: list[Any]) -> None:
|
||||
if args:
|
||||
raise CommandError("Too many arguments")
|
||||
raise CommandError('Too many arguments')
|
||||
|
||||
num_http_files = len(self._find_http_files(options))
|
||||
num_packages = len(self._find_wheels(options, "*"))
|
||||
num_packages = len(self._find_wheels(options, '*'))
|
||||
|
||||
http_cache_location = self._cache_dir(options, "http")
|
||||
wheels_cache_location = self._cache_dir(options, "wheels")
|
||||
http_cache_location = self._cache_dir(options, 'http')
|
||||
wheels_cache_location = self._cache_dir(options, 'wheels')
|
||||
http_cache_size = filesystem.format_directory_size(http_cache_location)
|
||||
wheels_cache_size = filesystem.format_directory_size(wheels_cache_location)
|
||||
|
||||
|
|
@ -108,7 +113,7 @@ class CacheCommand(Command):
|
|||
Wheels location: {wheels_cache_location}
|
||||
Wheels size: {wheels_cache_size}
|
||||
Number of wheels: {package_count}
|
||||
"""
|
||||
""",
|
||||
)
|
||||
.format(
|
||||
http_cache_location=http_cache_location,
|
||||
|
|
@ -123,35 +128,35 @@ class CacheCommand(Command):
|
|||
|
||||
logger.info(message)
|
||||
|
||||
def list_cache_items(self, options: Values, args: List[Any]) -> None:
|
||||
def list_cache_items(self, options: Values, args: list[Any]) -> None:
|
||||
if len(args) > 1:
|
||||
raise CommandError("Too many arguments")
|
||||
raise CommandError('Too many arguments')
|
||||
|
||||
if args:
|
||||
pattern = args[0]
|
||||
else:
|
||||
pattern = "*"
|
||||
pattern = '*'
|
||||
|
||||
files = self._find_wheels(options, pattern)
|
||||
if options.list_format == "human":
|
||||
if options.list_format == 'human':
|
||||
self.format_for_human(files)
|
||||
else:
|
||||
self.format_for_abspath(files)
|
||||
|
||||
def format_for_human(self, files: List[str]) -> None:
|
||||
def format_for_human(self, files: list[str]) -> None:
|
||||
if not files:
|
||||
logger.info("Nothing cached.")
|
||||
logger.info('Nothing cached.')
|
||||
return
|
||||
|
||||
results = []
|
||||
for filename in files:
|
||||
wheel = os.path.basename(filename)
|
||||
size = filesystem.format_file_size(filename)
|
||||
results.append(f" - {wheel} ({size})")
|
||||
logger.info("Cache contents:\n")
|
||||
logger.info("\n".join(sorted(results)))
|
||||
results.append(f' - {wheel} ({size})')
|
||||
logger.info('Cache contents:\n')
|
||||
logger.info('\n'.join(sorted(results)))
|
||||
|
||||
def format_for_abspath(self, files: List[str]) -> None:
|
||||
def format_for_abspath(self, files: list[str]) -> None:
|
||||
if not files:
|
||||
return
|
||||
|
||||
|
|
@ -159,48 +164,48 @@ class CacheCommand(Command):
|
|||
for filename in files:
|
||||
results.append(filename)
|
||||
|
||||
logger.info("\n".join(sorted(results)))
|
||||
logger.info('\n'.join(sorted(results)))
|
||||
|
||||
def remove_cache_items(self, options: Values, args: List[Any]) -> None:
|
||||
def remove_cache_items(self, options: Values, args: list[Any]) -> None:
|
||||
if len(args) > 1:
|
||||
raise CommandError("Too many arguments")
|
||||
raise CommandError('Too many arguments')
|
||||
|
||||
if not args:
|
||||
raise CommandError("Please provide a pattern")
|
||||
raise CommandError('Please provide a pattern')
|
||||
|
||||
files = self._find_wheels(options, args[0])
|
||||
|
||||
no_matching_msg = "No matching packages"
|
||||
if args[0] == "*":
|
||||
no_matching_msg = 'No matching packages'
|
||||
if args[0] == '*':
|
||||
# Only fetch http files if no specific pattern given
|
||||
files += self._find_http_files(options)
|
||||
else:
|
||||
# Add the pattern to the log message
|
||||
no_matching_msg += ' for pattern "{}"'.format(args[0])
|
||||
no_matching_msg += f' for pattern "{args[0]}"'
|
||||
|
||||
if not files:
|
||||
logger.warning(no_matching_msg)
|
||||
|
||||
for filename in files:
|
||||
os.unlink(filename)
|
||||
logger.verbose("Removed %s", filename)
|
||||
logger.info("Files removed: %s", len(files))
|
||||
logger.verbose('Removed %s', filename)
|
||||
logger.info('Files removed: %s', len(files))
|
||||
|
||||
def purge_cache(self, options: Values, args: List[Any]) -> None:
|
||||
def purge_cache(self, options: Values, args: list[Any]) -> None:
|
||||
if args:
|
||||
raise CommandError("Too many arguments")
|
||||
raise CommandError('Too many arguments')
|
||||
|
||||
return self.remove_cache_items(options, ["*"])
|
||||
return self.remove_cache_items(options, ['*'])
|
||||
|
||||
def _cache_dir(self, options: Values, subdir: str) -> str:
|
||||
return os.path.join(options.cache_dir, subdir)
|
||||
|
||||
def _find_http_files(self, options: Values) -> List[str]:
|
||||
http_dir = self._cache_dir(options, "http")
|
||||
return filesystem.find_files(http_dir, "*")
|
||||
def _find_http_files(self, options: Values) -> list[str]:
|
||||
http_dir = self._cache_dir(options, 'http')
|
||||
return filesystem.find_files(http_dir, '*')
|
||||
|
||||
def _find_wheels(self, options: Values, pattern: str) -> List[str]:
|
||||
wheel_dir = self._cache_dir(options, "wheels")
|
||||
def _find_wheels(self, options: Values, pattern: str) -> list[str]:
|
||||
wheel_dir = self._cache_dir(options, 'wheels')
|
||||
|
||||
# The wheel filename format, as specified in PEP 427, is:
|
||||
# {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl
|
||||
|
|
@ -218,6 +223,6 @@ class CacheCommand(Command):
|
|||
# match the hyphen before the version, followed by anything else.
|
||||
#
|
||||
# PEP 427: https://www.python.org/dev/peps/pep-0427/
|
||||
pattern = pattern + ("*.whl" if "-" in pattern else "-*.whl")
|
||||
pattern = pattern + ('*.whl' if '-' in pattern else '-*.whl')
|
||||
|
||||
return filesystem.find_files(wheel_dir, pattern)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from optparse import Values
|
||||
from typing import List
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
from pip._internal.operations.check import (
|
||||
check_package_set,
|
||||
create_package_set_from_installed,
|
||||
)
|
||||
from pip._internal.cli.status_codes import ERROR
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.operations.check import check_package_set
|
||||
from pip._internal.operations.check import create_package_set_from_installed
|
||||
from pip._internal.utils.misc import write_output
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -19,7 +20,7 @@ class CheckCommand(Command):
|
|||
usage = """
|
||||
%prog [options]"""
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
|
||||
package_set, parsing_probs = create_package_set_from_installed()
|
||||
missing, conflicting = check_package_set(package_set)
|
||||
|
|
@ -28,7 +29,7 @@ class CheckCommand(Command):
|
|||
version = package_set[project_name].version
|
||||
for dependency in missing[project_name]:
|
||||
write_output(
|
||||
"%s %s requires %s, which is not installed.",
|
||||
'%s %s requires %s, which is not installed.',
|
||||
project_name,
|
||||
version,
|
||||
dependency[0],
|
||||
|
|
@ -38,7 +39,7 @@ class CheckCommand(Command):
|
|||
version = package_set[project_name].version
|
||||
for dep_name, dep_version, req in conflicting[project_name]:
|
||||
write_output(
|
||||
"%s %s has requirement %s, but you have %s %s.",
|
||||
'%s %s has requirement %s, but you have %s %s.',
|
||||
project_name,
|
||||
version,
|
||||
req,
|
||||
|
|
@ -49,5 +50,5 @@ class CheckCommand(Command):
|
|||
if missing or conflicting or parsing_probs:
|
||||
return ERROR
|
||||
else:
|
||||
write_output("No broken requirements found.")
|
||||
write_output('No broken requirements found.')
|
||||
return SUCCESS
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import textwrap
|
||||
from optparse import Values
|
||||
|
|
@ -12,7 +14,7 @@ BASE_COMPLETION = """
|
|||
"""
|
||||
|
||||
COMPLETION_SCRIPTS = {
|
||||
"bash": """
|
||||
'bash': """
|
||||
_pip_completion()
|
||||
{{
|
||||
COMPREPLY=( $( COMP_WORDS="${{COMP_WORDS[*]}}" \\
|
||||
|
|
@ -21,7 +23,7 @@ COMPLETION_SCRIPTS = {
|
|||
}}
|
||||
complete -o default -F _pip_completion {prog}
|
||||
""",
|
||||
"zsh": """
|
||||
'zsh': """
|
||||
function _pip_completion {{
|
||||
local words cword
|
||||
read -Ac words
|
||||
|
|
@ -32,7 +34,7 @@ COMPLETION_SCRIPTS = {
|
|||
}}
|
||||
compctl -K _pip_completion {prog}
|
||||
""",
|
||||
"fish": """
|
||||
'fish': """
|
||||
function __fish_complete_pip
|
||||
set -lx COMP_WORDS (commandline -o) ""
|
||||
set -lx COMP_CWORD ( \\
|
||||
|
|
@ -53,44 +55,44 @@ class CompletionCommand(Command):
|
|||
|
||||
def add_options(self) -> None:
|
||||
self.cmd_opts.add_option(
|
||||
"--bash",
|
||||
"-b",
|
||||
action="store_const",
|
||||
const="bash",
|
||||
dest="shell",
|
||||
help="Emit completion code for bash",
|
||||
'--bash',
|
||||
'-b',
|
||||
action='store_const',
|
||||
const='bash',
|
||||
dest='shell',
|
||||
help='Emit completion code for bash',
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"--zsh",
|
||||
"-z",
|
||||
action="store_const",
|
||||
const="zsh",
|
||||
dest="shell",
|
||||
help="Emit completion code for zsh",
|
||||
'--zsh',
|
||||
'-z',
|
||||
action='store_const',
|
||||
const='zsh',
|
||||
dest='shell',
|
||||
help='Emit completion code for zsh',
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"--fish",
|
||||
"-f",
|
||||
action="store_const",
|
||||
const="fish",
|
||||
dest="shell",
|
||||
help="Emit completion code for fish",
|
||||
'--fish',
|
||||
'-f',
|
||||
action='store_const',
|
||||
const='fish',
|
||||
dest='shell',
|
||||
help='Emit completion code for fish',
|
||||
)
|
||||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
"""Prints the completion code of the given shell"""
|
||||
shells = COMPLETION_SCRIPTS.keys()
|
||||
shell_options = ["--" + shell for shell in sorted(shells)]
|
||||
shell_options = ['--' + shell for shell in sorted(shells)]
|
||||
if options.shell in shells:
|
||||
script = textwrap.dedent(
|
||||
COMPLETION_SCRIPTS.get(options.shell, "").format(prog=get_prog())
|
||||
COMPLETION_SCRIPTS.get(options.shell, '').format(prog=get_prog()),
|
||||
)
|
||||
print(BASE_COMPLETION.format(script=script, shell=options.shell))
|
||||
return SUCCESS
|
||||
else:
|
||||
sys.stderr.write(
|
||||
"ERROR: You must pass {}\n".format(" or ".join(shell_options))
|
||||
'ERROR: You must pass {}\n'.format(' or '.join(shell_options)),
|
||||
)
|
||||
return SUCCESS
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
from optparse import Values
|
||||
from typing import Any, List, Optional
|
||||
from typing import Any
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
from pip._internal.configuration import (
|
||||
Configuration,
|
||||
Kind,
|
||||
get_configuration_files,
|
||||
kinds,
|
||||
)
|
||||
from pip._internal.cli.status_codes import ERROR
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.configuration import Configuration
|
||||
from pip._internal.configuration import get_configuration_files
|
||||
from pip._internal.configuration import Kind
|
||||
from pip._internal.configuration import kinds
|
||||
from pip._internal.exceptions import PipError
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import get_prog, write_output
|
||||
from pip._internal.utils.misc import get_prog
|
||||
from pip._internal.utils.misc import write_output
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -51,57 +55,57 @@ class ConfigurationCommand(Command):
|
|||
|
||||
def add_options(self) -> None:
|
||||
self.cmd_opts.add_option(
|
||||
"--editor",
|
||||
dest="editor",
|
||||
action="store",
|
||||
'--editor',
|
||||
dest='editor',
|
||||
action='store',
|
||||
default=None,
|
||||
help=(
|
||||
"Editor to use to edit the file. Uses VISUAL or EDITOR "
|
||||
"environment variables if not provided."
|
||||
'Editor to use to edit the file. Uses VISUAL or EDITOR '
|
||||
'environment variables if not provided.'
|
||||
),
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--global",
|
||||
dest="global_file",
|
||||
action="store_true",
|
||||
'--global',
|
||||
dest='global_file',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Use the system-wide configuration file only",
|
||||
help='Use the system-wide configuration file only',
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--user",
|
||||
dest="user_file",
|
||||
action="store_true",
|
||||
'--user',
|
||||
dest='user_file',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Use the user configuration file only",
|
||||
help='Use the user configuration file only',
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--site",
|
||||
dest="site_file",
|
||||
action="store_true",
|
||||
'--site',
|
||||
dest='site_file',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Use the current environment configuration file only",
|
||||
help='Use the current environment configuration file only',
|
||||
)
|
||||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
handlers = {
|
||||
"list": self.list_values,
|
||||
"edit": self.open_in_editor,
|
||||
"get": self.get_name,
|
||||
"set": self.set_name_value,
|
||||
"unset": self.unset_name,
|
||||
"debug": self.list_config_values,
|
||||
'list': self.list_values,
|
||||
'edit': self.open_in_editor,
|
||||
'get': self.get_name,
|
||||
'set': self.set_name_value,
|
||||
'unset': self.unset_name,
|
||||
'debug': self.list_config_values,
|
||||
}
|
||||
|
||||
# Determine action
|
||||
if not args or args[0] not in handlers:
|
||||
logger.error(
|
||||
"Need an action (%s) to perform.",
|
||||
", ".join(sorted(handlers)),
|
||||
'Need an action (%s) to perform.',
|
||||
', '.join(sorted(handlers)),
|
||||
)
|
||||
return ERROR
|
||||
|
||||
|
|
@ -111,7 +115,7 @@ class ConfigurationCommand(Command):
|
|||
# Depends on whether the command is modifying.
|
||||
try:
|
||||
load_only = self._determine_file(
|
||||
options, need_value=(action in ["get", "set", "unset", "edit"])
|
||||
options, need_value=(action in ['get', 'set', 'unset', 'edit']),
|
||||
)
|
||||
except PipError as e:
|
||||
logger.error(e.args[0])
|
||||
|
|
@ -119,7 +123,7 @@ class ConfigurationCommand(Command):
|
|||
|
||||
# Load a new configuration
|
||||
self.configuration = Configuration(
|
||||
isolated=options.isolated_mode, load_only=load_only
|
||||
isolated=options.isolated_mode, load_only=load_only,
|
||||
)
|
||||
self.configuration.load()
|
||||
|
||||
|
|
@ -132,7 +136,7 @@ class ConfigurationCommand(Command):
|
|||
|
||||
return SUCCESS
|
||||
|
||||
def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]:
|
||||
def _determine_file(self, options: Values, need_value: bool) -> Kind | None:
|
||||
file_options = [
|
||||
key
|
||||
for key, value in (
|
||||
|
|
@ -158,47 +162,47 @@ class ConfigurationCommand(Command):
|
|||
return file_options[0]
|
||||
|
||||
raise PipError(
|
||||
"Need exactly one file to operate upon "
|
||||
"(--user, --site, --global) to perform."
|
||||
'Need exactly one file to operate upon '
|
||||
'(--user, --site, --global) to perform.',
|
||||
)
|
||||
|
||||
def list_values(self, options: Values, args: List[str]) -> None:
|
||||
self._get_n_args(args, "list", n=0)
|
||||
def list_values(self, options: Values, args: list[str]) -> None:
|
||||
self._get_n_args(args, 'list', n=0)
|
||||
|
||||
for key, value in sorted(self.configuration.items()):
|
||||
write_output("%s=%r", key, value)
|
||||
write_output('%s=%r', key, value)
|
||||
|
||||
def get_name(self, options: Values, args: List[str]) -> None:
|
||||
key = self._get_n_args(args, "get [name]", n=1)
|
||||
def get_name(self, options: Values, args: list[str]) -> None:
|
||||
key = self._get_n_args(args, 'get [name]', n=1)
|
||||
value = self.configuration.get_value(key)
|
||||
|
||||
write_output("%s", value)
|
||||
write_output('%s', value)
|
||||
|
||||
def set_name_value(self, options: Values, args: List[str]) -> None:
|
||||
key, value = self._get_n_args(args, "set [name] [value]", n=2)
|
||||
def set_name_value(self, options: Values, args: list[str]) -> None:
|
||||
key, value = self._get_n_args(args, 'set [name] [value]', n=2)
|
||||
self.configuration.set_value(key, value)
|
||||
|
||||
self._save_configuration()
|
||||
|
||||
def unset_name(self, options: Values, args: List[str]) -> None:
|
||||
key = self._get_n_args(args, "unset [name]", n=1)
|
||||
def unset_name(self, options: Values, args: list[str]) -> None:
|
||||
key = self._get_n_args(args, 'unset [name]', n=1)
|
||||
self.configuration.unset_value(key)
|
||||
|
||||
self._save_configuration()
|
||||
|
||||
def list_config_values(self, options: Values, args: List[str]) -> None:
|
||||
def list_config_values(self, options: Values, args: list[str]) -> None:
|
||||
"""List config key-value pairs across different config files"""
|
||||
self._get_n_args(args, "debug", n=0)
|
||||
self._get_n_args(args, 'debug', n=0)
|
||||
|
||||
self.print_env_var_values()
|
||||
# Iterate over config files and print if they exist, and the
|
||||
# key-value pairs present in them if they do
|
||||
for variant, files in sorted(self.configuration.iter_config_files()):
|
||||
write_output("%s:", variant)
|
||||
write_output('%s:', variant)
|
||||
for fname in files:
|
||||
with indent_log():
|
||||
file_exists = os.path.exists(fname)
|
||||
write_output("%s, exists: %r", fname, file_exists)
|
||||
write_output('%s, exists: %r', fname, file_exists)
|
||||
if file_exists:
|
||||
self.print_config_file_values(variant)
|
||||
|
||||
|
|
@ -206,35 +210,35 @@ class ConfigurationCommand(Command):
|
|||
"""Get key-value pairs from the file of a variant"""
|
||||
for name, value in self.configuration.get_values_in_config(variant).items():
|
||||
with indent_log():
|
||||
write_output("%s: %s", name, value)
|
||||
write_output('%s: %s', name, value)
|
||||
|
||||
def print_env_var_values(self) -> None:
|
||||
"""Get key-values pairs present as environment variables"""
|
||||
write_output("%s:", "env_var")
|
||||
write_output('%s:', 'env_var')
|
||||
with indent_log():
|
||||
for key, value in sorted(self.configuration.get_environ_vars()):
|
||||
env_var = f"PIP_{key.upper()}"
|
||||
write_output("%s=%r", env_var, value)
|
||||
env_var = f'PIP_{key.upper()}'
|
||||
write_output('%s=%r', env_var, value)
|
||||
|
||||
def open_in_editor(self, options: Values, args: List[str]) -> None:
|
||||
def open_in_editor(self, options: Values, args: list[str]) -> None:
|
||||
editor = self._determine_editor(options)
|
||||
|
||||
fname = self.configuration.get_file_to_edit()
|
||||
if fname is None:
|
||||
raise PipError("Could not determine appropriate file.")
|
||||
raise PipError('Could not determine appropriate file.')
|
||||
|
||||
try:
|
||||
subprocess.check_call([editor, fname])
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise PipError(
|
||||
"Editor Subprocess exited with exit code {}".format(e.returncode)
|
||||
f'Editor Subprocess exited with exit code {e.returncode}',
|
||||
)
|
||||
|
||||
def _get_n_args(self, args: List[str], example: str, n: int) -> Any:
|
||||
def _get_n_args(self, args: list[str], example: str, n: int) -> Any:
|
||||
"""Helper to make sure the command got the right number of arguments"""
|
||||
if len(args) != n:
|
||||
msg = (
|
||||
"Got unexpected number of arguments, expected {}. "
|
||||
'Got unexpected number of arguments, expected {}. '
|
||||
'(example: "{} config {}")'
|
||||
).format(n, get_prog(), example)
|
||||
raise PipError(msg)
|
||||
|
|
@ -251,16 +255,16 @@ class ConfigurationCommand(Command):
|
|||
self.configuration.save()
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Unable to save configuration. Please report this as a bug."
|
||||
'Unable to save configuration. Please report this as a bug.',
|
||||
)
|
||||
raise PipError("Internal Error.")
|
||||
raise PipError('Internal Error.')
|
||||
|
||||
def _determine_editor(self, options: Values) -> str:
|
||||
if options.editor is not None:
|
||||
return options.editor
|
||||
elif "VISUAL" in os.environ:
|
||||
return os.environ["VISUAL"]
|
||||
elif "EDITOR" in os.environ:
|
||||
return os.environ["EDITOR"]
|
||||
elif 'VISUAL' in os.environ:
|
||||
return os.environ['VISUAL']
|
||||
elif 'EDITOR' in os.environ:
|
||||
return os.environ['EDITOR']
|
||||
else:
|
||||
raise PipError("Could not determine editor to use.")
|
||||
raise PipError('Could not determine editor to use.')
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from optparse import Values
|
||||
from types import ModuleType
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
import pip._vendor
|
||||
from pip._vendor.certifi import where
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
|
||||
from pip import __file__ as pip_location
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.base_command import Command
|
||||
|
|
@ -19,51 +21,53 @@ from pip._internal.configuration import Configuration
|
|||
from pip._internal.metadata import get_environment
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import get_pip_version
|
||||
from pip._vendor.certifi import where
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def show_value(name: str, value: Any) -> None:
|
||||
logger.info("%s: %s", name, value)
|
||||
logger.info('%s: %s', name, value)
|
||||
|
||||
|
||||
def show_sys_implementation() -> None:
|
||||
logger.info("sys.implementation:")
|
||||
logger.info('sys.implementation:')
|
||||
implementation_name = sys.implementation.name
|
||||
with indent_log():
|
||||
show_value("name", implementation_name)
|
||||
show_value('name', implementation_name)
|
||||
|
||||
|
||||
def create_vendor_txt_map() -> Dict[str, str]:
|
||||
def create_vendor_txt_map() -> dict[str, str]:
|
||||
vendor_txt_path = os.path.join(
|
||||
os.path.dirname(pip_location), "_vendor", "vendor.txt"
|
||||
os.path.dirname(pip_location), '_vendor', 'vendor.txt',
|
||||
)
|
||||
|
||||
with open(vendor_txt_path) as f:
|
||||
# Purge non version specifying lines.
|
||||
# Also, remove any space prefix or suffixes (including comments).
|
||||
lines = [
|
||||
line.strip().split(" ", 1)[0] for line in f.readlines() if "==" in line
|
||||
line.strip().split(' ', 1)[0] for line in f.readlines() if '==' in line
|
||||
]
|
||||
|
||||
# Transform into "module" -> version dict.
|
||||
return dict(line.split("==", 1) for line in lines) # type: ignore
|
||||
return dict(line.split('==', 1) for line in lines) # type: ignore
|
||||
|
||||
|
||||
def get_module_from_module_name(module_name: str) -> ModuleType:
|
||||
# Module name can be uppercase in vendor.txt for some reason...
|
||||
module_name = module_name.lower()
|
||||
# PATCH: setuptools is actually only pkg_resources.
|
||||
if module_name == "setuptools":
|
||||
module_name = "pkg_resources"
|
||||
if module_name == 'setuptools':
|
||||
module_name = 'pkg_resources'
|
||||
|
||||
__import__(f"pip._vendor.{module_name}", globals(), locals(), level=0)
|
||||
__import__(f'pip._vendor.{module_name}', globals(), locals(), level=0)
|
||||
return getattr(pip._vendor, module_name)
|
||||
|
||||
|
||||
def get_vendor_version_from_module(module_name: str) -> Optional[str]:
|
||||
def get_vendor_version_from_module(module_name: str) -> str | None:
|
||||
module = get_module_from_module_name(module_name)
|
||||
version = getattr(module, "__version__", None)
|
||||
version = getattr(module, '__version__', None)
|
||||
|
||||
if not version:
|
||||
# Try to find version in debundled module info.
|
||||
|
|
@ -75,29 +79,29 @@ def get_vendor_version_from_module(module_name: str) -> Optional[str]:
|
|||
return version
|
||||
|
||||
|
||||
def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None:
|
||||
def show_actual_vendor_versions(vendor_txt_versions: dict[str, str]) -> None:
|
||||
"""Log the actual version and print extra info if there is
|
||||
a conflict or if the actual version could not be imported.
|
||||
"""
|
||||
for module_name, expected_version in vendor_txt_versions.items():
|
||||
extra_message = ""
|
||||
extra_message = ''
|
||||
actual_version = get_vendor_version_from_module(module_name)
|
||||
if not actual_version:
|
||||
extra_message = (
|
||||
" (Unable to locate actual module version, using"
|
||||
" vendor.txt specified version)"
|
||||
' (Unable to locate actual module version, using'
|
||||
' vendor.txt specified version)'
|
||||
)
|
||||
actual_version = expected_version
|
||||
elif parse_version(actual_version) != parse_version(expected_version):
|
||||
extra_message = (
|
||||
" (CONFLICT: vendor.txt suggests version should"
|
||||
" be {})".format(expected_version)
|
||||
' (CONFLICT: vendor.txt suggests version should'
|
||||
' be {})'.format(expected_version)
|
||||
)
|
||||
logger.info("%s==%s%s", module_name, actual_version, extra_message)
|
||||
logger.info('%s==%s%s', module_name, actual_version, extra_message)
|
||||
|
||||
|
||||
def show_vendor_versions() -> None:
|
||||
logger.info("vendored library versions:")
|
||||
logger.info('vendored library versions:')
|
||||
|
||||
vendor_txt_versions = create_vendor_txt_map()
|
||||
with indent_log():
|
||||
|
|
@ -112,11 +116,11 @@ def show_tags(options: Values) -> None:
|
|||
|
||||
# Display the target options that were explicitly provided.
|
||||
formatted_target = target_python.format_given()
|
||||
suffix = ""
|
||||
suffix = ''
|
||||
if formatted_target:
|
||||
suffix = f" (target: {formatted_target})"
|
||||
suffix = f' (target: {formatted_target})'
|
||||
|
||||
msg = "Compatible tags: {}{}".format(len(tags), suffix)
|
||||
msg = f'Compatible tags: {len(tags)}{suffix}'
|
||||
logger.info(msg)
|
||||
|
||||
if options.verbose < 1 and len(tags) > tag_limit:
|
||||
|
|
@ -131,7 +135,7 @@ def show_tags(options: Values) -> None:
|
|||
|
||||
if tags_limited:
|
||||
msg = (
|
||||
"...\n[First {tag_limit} tags shown. Pass --verbose to show all.]"
|
||||
'...\n[First {tag_limit} tags shown. Pass --verbose to show all.]'
|
||||
).format(tag_limit=tag_limit)
|
||||
logger.info(msg)
|
||||
|
||||
|
|
@ -139,21 +143,21 @@ def show_tags(options: Values) -> None:
|
|||
def ca_bundle_info(config: Configuration) -> str:
|
||||
levels = set()
|
||||
for key, _ in config.items():
|
||||
levels.add(key.split(".")[0])
|
||||
levels.add(key.split('.')[0])
|
||||
|
||||
if not levels:
|
||||
return "Not specified"
|
||||
return 'Not specified'
|
||||
|
||||
levels_that_override_global = ["install", "wheel", "download"]
|
||||
levels_that_override_global = ['install', 'wheel', 'download']
|
||||
global_overriding_level = [
|
||||
level for level in levels if level in levels_that_override_global
|
||||
]
|
||||
if not global_overriding_level:
|
||||
return "global"
|
||||
return 'global'
|
||||
|
||||
if "global" in levels:
|
||||
levels.remove("global")
|
||||
return ", ".join(levels)
|
||||
if 'global' in levels:
|
||||
levels.remove('global')
|
||||
return ', '.join(levels)
|
||||
|
||||
|
||||
class DebugCommand(Command):
|
||||
|
|
@ -170,30 +174,30 @@ class DebugCommand(Command):
|
|||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
self.parser.config.load()
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
logger.warning(
|
||||
"This command is only meant for debugging. "
|
||||
"Do not use this with automation for parsing and getting these "
|
||||
"details, since the output and options of this command may "
|
||||
"change without notice."
|
||||
'This command is only meant for debugging. '
|
||||
'Do not use this with automation for parsing and getting these '
|
||||
'details, since the output and options of this command may '
|
||||
'change without notice.',
|
||||
)
|
||||
show_value("pip version", get_pip_version())
|
||||
show_value("sys.version", sys.version)
|
||||
show_value("sys.executable", sys.executable)
|
||||
show_value("sys.getdefaultencoding", sys.getdefaultencoding())
|
||||
show_value("sys.getfilesystemencoding", sys.getfilesystemencoding())
|
||||
show_value('pip version', get_pip_version())
|
||||
show_value('sys.version', sys.version)
|
||||
show_value('sys.executable', sys.executable)
|
||||
show_value('sys.getdefaultencoding', sys.getdefaultencoding())
|
||||
show_value('sys.getfilesystemencoding', sys.getfilesystemencoding())
|
||||
show_value(
|
||||
"locale.getpreferredencoding",
|
||||
'locale.getpreferredencoding',
|
||||
locale.getpreferredencoding(),
|
||||
)
|
||||
show_value("sys.platform", sys.platform)
|
||||
show_value('sys.platform', sys.platform)
|
||||
show_sys_implementation()
|
||||
|
||||
show_value("'cert' config value", ca_bundle_info(self.parser.config))
|
||||
show_value("REQUESTS_CA_BUNDLE", os.environ.get("REQUESTS_CA_BUNDLE"))
|
||||
show_value("CURL_CA_BUNDLE", os.environ.get("CURL_CA_BUNDLE"))
|
||||
show_value("pip._vendor.certifi.where()", where())
|
||||
show_value("pip._vendor.DEBUNDLED", pip._vendor.DEBUNDLED)
|
||||
show_value('REQUESTS_CA_BUNDLE', os.environ.get('REQUESTS_CA_BUNDLE'))
|
||||
show_value('CURL_CA_BUNDLE', os.environ.get('CURL_CA_BUNDLE'))
|
||||
show_value('pip._vendor.certifi.where()', where())
|
||||
show_value('pip._vendor.DEBUNDLED', pip._vendor.DEBUNDLED)
|
||||
|
||||
show_vendor_versions()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from optparse import Values
|
||||
|
|
@ -5,10 +7,13 @@ from typing import List
|
|||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.cmdoptions import make_target_python
|
||||
from pip._internal.cli.req_command import RequirementCommand, with_cleanup
|
||||
from pip._internal.cli.req_command import RequirementCommand
|
||||
from pip._internal.cli.req_command import with_cleanup
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.req.req_tracker import get_requirement_tracker
|
||||
from pip._internal.utils.misc import ensure_dir, normalize_path, write_output
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
from pip._internal.utils.misc import normalize_path
|
||||
from pip._internal.utils.misc import write_output
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -52,14 +57,14 @@ class DownloadCommand(RequirementCommand):
|
|||
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"-d",
|
||||
"--dest",
|
||||
"--destination-dir",
|
||||
"--destination-directory",
|
||||
dest="download_dir",
|
||||
metavar="dir",
|
||||
'-d',
|
||||
'--dest',
|
||||
'--destination-dir',
|
||||
'--destination-directory',
|
||||
dest='download_dir',
|
||||
metavar='dir',
|
||||
default=os.curdir,
|
||||
help="Download packages into <dir>.",
|
||||
help='Download packages into <dir>.',
|
||||
)
|
||||
|
||||
cmdoptions.add_target_python_options(self.cmd_opts)
|
||||
|
|
@ -73,7 +78,7 @@ class DownloadCommand(RequirementCommand):
|
|||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
@with_cleanup
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
|
||||
options.ignore_installed = True
|
||||
# editable doesn't really make sense for `pip download`, but the bowels
|
||||
|
|
@ -99,7 +104,7 @@ class DownloadCommand(RequirementCommand):
|
|||
|
||||
directory = TempDirectory(
|
||||
delete=not options.no_clean,
|
||||
kind="download",
|
||||
kind='download',
|
||||
globally_managed=True,
|
||||
)
|
||||
|
||||
|
|
@ -128,13 +133,13 @@ class DownloadCommand(RequirementCommand):
|
|||
|
||||
requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
|
||||
|
||||
downloaded: List[str] = []
|
||||
downloaded: list[str] = []
|
||||
for req in requirement_set.requirements.values():
|
||||
if req.satisfied_by is None:
|
||||
assert req.name is not None
|
||||
preparer.save_linked_requirement(req)
|
||||
downloaded.append(req.name)
|
||||
if downloaded:
|
||||
write_output("Successfully downloaded %s", " ".join(downloaded))
|
||||
write_output('Successfully downloaded %s', ' '.join(downloaded))
|
||||
|
||||
return SUCCESS
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from optparse import Values
|
||||
from typing import List
|
||||
|
|
@ -8,7 +10,7 @@ from pip._internal.cli.status_codes import SUCCESS
|
|||
from pip._internal.operations.freeze import freeze
|
||||
from pip._internal.utils.compat import stdlib_pkgs
|
||||
|
||||
DEV_PKGS = {"pip", "setuptools", "distribute", "wheel"}
|
||||
DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'}
|
||||
|
||||
|
||||
class FreezeCommand(Command):
|
||||
|
|
@ -20,61 +22,61 @@ class FreezeCommand(Command):
|
|||
|
||||
usage = """
|
||||
%prog [options]"""
|
||||
log_streams = ("ext://sys.stderr", "ext://sys.stderr")
|
||||
log_streams = ('ext://sys.stderr', 'ext://sys.stderr')
|
||||
|
||||
def add_options(self) -> None:
|
||||
self.cmd_opts.add_option(
|
||||
"-r",
|
||||
"--requirement",
|
||||
dest="requirements",
|
||||
action="append",
|
||||
'-r',
|
||||
'--requirement',
|
||||
dest='requirements',
|
||||
action='append',
|
||||
default=[],
|
||||
metavar="file",
|
||||
metavar='file',
|
||||
help=(
|
||||
"Use the order in the given requirements file and its "
|
||||
"comments when generating output. This option can be "
|
||||
"used multiple times."
|
||||
'Use the order in the given requirements file and its '
|
||||
'comments when generating output. This option can be '
|
||||
'used multiple times.'
|
||||
),
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"-l",
|
||||
"--local",
|
||||
dest="local",
|
||||
action="store_true",
|
||||
'-l',
|
||||
'--local',
|
||||
dest='local',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=(
|
||||
"If in a virtualenv that has global access, do not output "
|
||||
"globally-installed packages."
|
||||
'If in a virtualenv that has global access, do not output '
|
||||
'globally-installed packages.'
|
||||
),
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"--user",
|
||||
dest="user",
|
||||
action="store_true",
|
||||
'--user',
|
||||
dest='user',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Only output packages installed in user-site.",
|
||||
help='Only output packages installed in user-site.',
|
||||
)
|
||||
self.cmd_opts.add_option(cmdoptions.list_path())
|
||||
self.cmd_opts.add_option(
|
||||
"--all",
|
||||
dest="freeze_all",
|
||||
action="store_true",
|
||||
'--all',
|
||||
dest='freeze_all',
|
||||
action='store_true',
|
||||
help=(
|
||||
"Do not skip these packages in the output:"
|
||||
" {}".format(", ".join(DEV_PKGS))
|
||||
'Do not skip these packages in the output:'
|
||||
' {}'.format(', '.join(DEV_PKGS))
|
||||
),
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"--exclude-editable",
|
||||
dest="exclude_editable",
|
||||
action="store_true",
|
||||
help="Exclude editable package from output.",
|
||||
'--exclude-editable',
|
||||
dest='exclude_editable',
|
||||
action='store_true',
|
||||
help='Exclude editable package from output.',
|
||||
)
|
||||
self.cmd_opts.add_option(cmdoptions.list_exclude())
|
||||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
skip = set(stdlib_pkgs)
|
||||
if not options.freeze_all:
|
||||
skip.update(DEV_PKGS)
|
||||
|
|
@ -93,5 +95,5 @@ class FreezeCommand(Command):
|
|||
skip=skip,
|
||||
exclude_editable=options.exclude_editable,
|
||||
):
|
||||
sys.stdout.write(line + "\n")
|
||||
sys.stdout.write(line + '\n')
|
||||
return SUCCESS
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import sys
|
||||
|
|
@ -5,9 +7,12 @@ from optparse import Values
|
|||
from typing import List
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
from pip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES
|
||||
from pip._internal.utils.misc import read_chunks, write_output
|
||||
from pip._internal.cli.status_codes import ERROR
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.utils.hashes import FAVORITE_HASH
|
||||
from pip._internal.utils.hashes import STRONG_HASHES
|
||||
from pip._internal.utils.misc import read_chunks
|
||||
from pip._internal.utils.misc import write_output
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -20,24 +25,24 @@ class HashCommand(Command):
|
|||
installs.
|
||||
"""
|
||||
|
||||
usage = "%prog [options] <file> ..."
|
||||
usage = '%prog [options] <file> ...'
|
||||
ignore_require_venv = True
|
||||
|
||||
def add_options(self) -> None:
|
||||
self.cmd_opts.add_option(
|
||||
"-a",
|
||||
"--algorithm",
|
||||
dest="algorithm",
|
||||
'-a',
|
||||
'--algorithm',
|
||||
dest='algorithm',
|
||||
choices=STRONG_HASHES,
|
||||
action="store",
|
||||
action='store',
|
||||
default=FAVORITE_HASH,
|
||||
help="The hash algorithm to use: one of {}".format(
|
||||
", ".join(STRONG_HASHES)
|
||||
help='The hash algorithm to use: one of {}'.format(
|
||||
', '.join(STRONG_HASHES),
|
||||
),
|
||||
)
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
if not args:
|
||||
self.parser.print_usage(sys.stderr)
|
||||
return ERROR
|
||||
|
|
@ -45,14 +50,14 @@ class HashCommand(Command):
|
|||
algorithm = options.algorithm
|
||||
for path in args:
|
||||
write_output(
|
||||
"%s:\n--hash=%s:%s", path, algorithm, _hash_of_file(path, algorithm)
|
||||
'%s:\n--hash=%s:%s', path, algorithm, _hash_of_file(path, algorithm),
|
||||
)
|
||||
return SUCCESS
|
||||
|
||||
|
||||
def _hash_of_file(path: str, algorithm: str) -> str:
|
||||
"""Return the hash digest of a file."""
|
||||
with open(path, "rb") as archive:
|
||||
with open(path, 'rb') as archive:
|
||||
hash = hashlib.new(algorithm)
|
||||
for chunk in read_chunks(archive):
|
||||
hash.update(chunk)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from optparse import Values
|
||||
from typing import List
|
||||
|
||||
|
|
@ -13,7 +15,7 @@ class HelpCommand(Command):
|
|||
%prog <command>"""
|
||||
ignore_require_venv = True
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
from pip._internal.commands import (
|
||||
commands_dict,
|
||||
create_command,
|
||||
|
|
@ -33,7 +35,7 @@ class HelpCommand(Command):
|
|||
if guess:
|
||||
msg.append(f'maybe you meant "{guess}"')
|
||||
|
||||
raise CommandError(" - ".join(msg))
|
||||
raise CommandError(' - '.join(msg))
|
||||
|
||||
command = create_command(cmd_name)
|
||||
command.parser.print_help()
|
||||
|
|
|
|||
|
|
@ -1,20 +1,29 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from optparse import Values
|
||||
from typing import Any, Iterable, List, Optional, Union
|
||||
|
||||
from pip._vendor.packaging.version import LegacyVersion, Version
|
||||
from typing import Any
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.req_command import IndexGroupCommand
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
from pip._internal.cli.status_codes import ERROR
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.commands.search import print_dist_installation_info
|
||||
from pip._internal.exceptions import CommandError, DistributionNotFound, PipError
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.exceptions import DistributionNotFound
|
||||
from pip._internal.exceptions import PipError
|
||||
from pip._internal.index.collector import LinkCollector
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.models.selection_prefs import SelectionPreferences
|
||||
from pip._internal.models.target_python import TargetPython
|
||||
from pip._internal.network.session import PipSession
|
||||
from pip._internal.utils.misc import write_output
|
||||
from pip._vendor.packaging.version import LegacyVersion
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -44,22 +53,22 @@ class IndexCommand(IndexGroupCommand):
|
|||
self.parser.insert_option_group(0, index_opts)
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
handlers = {
|
||||
"versions": self.get_available_package_versions,
|
||||
'versions': self.get_available_package_versions,
|
||||
}
|
||||
|
||||
logger.warning(
|
||||
"pip index is currently an experimental command. "
|
||||
"It may be removed/changed in a future release "
|
||||
"without prior warning."
|
||||
'pip index is currently an experimental command. '
|
||||
'It may be removed/changed in a future release '
|
||||
'without prior warning.',
|
||||
)
|
||||
|
||||
# Determine action
|
||||
if not args or args[0] not in handlers:
|
||||
logger.error(
|
||||
"Need an action (%s) to perform.",
|
||||
", ".join(sorted(handlers)),
|
||||
'Need an action (%s) to perform.',
|
||||
', '.join(sorted(handlers)),
|
||||
)
|
||||
return ERROR
|
||||
|
||||
|
|
@ -78,8 +87,8 @@ class IndexCommand(IndexGroupCommand):
|
|||
self,
|
||||
options: Values,
|
||||
session: PipSession,
|
||||
target_python: Optional[TargetPython] = None,
|
||||
ignore_requires_python: Optional[bool] = None,
|
||||
target_python: TargetPython | None = None,
|
||||
ignore_requires_python: bool | None = None,
|
||||
) -> PackageFinder:
|
||||
"""
|
||||
Create a package finder appropriate to the index command.
|
||||
|
|
@ -97,12 +106,12 @@ class IndexCommand(IndexGroupCommand):
|
|||
link_collector=link_collector,
|
||||
selection_prefs=selection_prefs,
|
||||
target_python=target_python,
|
||||
use_deprecated_html5lib="html5lib" in options.deprecated_features_enabled,
|
||||
use_deprecated_html5lib='html5lib' in options.deprecated_features_enabled,
|
||||
)
|
||||
|
||||
def get_available_package_versions(self, options: Values, args: List[Any]) -> None:
|
||||
def get_available_package_versions(self, options: Values, args: list[Any]) -> None:
|
||||
if len(args) != 1:
|
||||
raise CommandError("You need to specify exactly one argument")
|
||||
raise CommandError('You need to specify exactly one argument')
|
||||
|
||||
target_python = cmdoptions.make_target_python(options)
|
||||
query = args[0]
|
||||
|
|
@ -115,7 +124,7 @@ class IndexCommand(IndexGroupCommand):
|
|||
ignore_requires_python=options.ignore_requires_python,
|
||||
)
|
||||
|
||||
versions: Iterable[Union[LegacyVersion, Version]] = (
|
||||
versions: Iterable[LegacyVersion | Version] = (
|
||||
candidate.version for candidate in finder.find_all_candidates(query)
|
||||
)
|
||||
|
||||
|
|
@ -128,12 +137,12 @@ class IndexCommand(IndexGroupCommand):
|
|||
|
||||
if not versions:
|
||||
raise DistributionNotFound(
|
||||
"No matching distribution found for {}".format(query)
|
||||
f'No matching distribution found for {query}',
|
||||
)
|
||||
|
||||
formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)]
|
||||
latest = formatted_versions[0]
|
||||
|
||||
write_output("{} ({})".format(query, latest))
|
||||
write_output("Available versions: {}".format(", ".join(formatted_versions)))
|
||||
write_output(f'{query} ({latest})')
|
||||
write_output('Available versions: {}'.format(', '.join(formatted_versions)))
|
||||
print_dist_installation_info(query, latest)
|
||||
|
|
|
|||
|
|
@ -1,27 +1,31 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import errno
|
||||
import operator
|
||||
import os
|
||||
import shutil
|
||||
import site
|
||||
from optparse import SUPPRESS_HELP, Values
|
||||
from typing import Iterable, List, Optional
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from optparse import SUPPRESS_HELP
|
||||
from optparse import Values
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.cache import WheelCache
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.cmdoptions import make_target_python
|
||||
from pip._internal.cli.req_command import (
|
||||
RequirementCommand,
|
||||
warn_if_run_as_root,
|
||||
with_cleanup,
|
||||
)
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
from pip._internal.exceptions import CommandError, InstallationError
|
||||
from pip._internal.cli.req_command import RequirementCommand
|
||||
from pip._internal.cli.req_command import warn_if_run_as_root
|
||||
from pip._internal.cli.req_command import with_cleanup
|
||||
from pip._internal.cli.status_codes import ERROR
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.locations import get_scheme
|
||||
from pip._internal.metadata import get_environment
|
||||
from pip._internal.models.format_control import FormatControl
|
||||
from pip._internal.operations.check import ConflictDetails, check_install_conflicts
|
||||
from pip._internal.operations.check import check_install_conflicts
|
||||
from pip._internal.operations.check import ConflictDetails
|
||||
from pip._internal.req import install_given_reqs
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.req.req_tracker import get_requirement_tracker
|
||||
|
|
@ -29,31 +33,26 @@ from pip._internal.utils.compat import WINDOWS
|
|||
from pip._internal.utils.distutils_args import parse_distutils_args
|
||||
from pip._internal.utils.filesystem import test_writable_dir
|
||||
from pip._internal.utils.logging import getLogger
|
||||
from pip._internal.utils.misc import (
|
||||
ensure_dir,
|
||||
get_pip_version,
|
||||
protect_pip_from_modification_on_windows,
|
||||
write_output,
|
||||
)
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
from pip._internal.utils.misc import get_pip_version
|
||||
from pip._internal.utils.misc import protect_pip_from_modification_on_windows
|
||||
from pip._internal.utils.misc import write_output
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._internal.utils.virtualenv import (
|
||||
running_under_virtualenv,
|
||||
virtualenv_no_global,
|
||||
)
|
||||
from pip._internal.wheel_builder import (
|
||||
BinaryAllowedPredicate,
|
||||
build,
|
||||
should_build_for_install_command,
|
||||
)
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
from pip._internal.utils.virtualenv import virtualenv_no_global
|
||||
from pip._internal.wheel_builder import BinaryAllowedPredicate
|
||||
from pip._internal.wheel_builder import build
|
||||
from pip._internal.wheel_builder import should_build_for_install_command
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
def get_check_binary_allowed(format_control: FormatControl) -> BinaryAllowedPredicate:
|
||||
def check_binary_allowed(req: InstallRequirement) -> bool:
|
||||
canonical_name = canonicalize_name(req.name or "")
|
||||
canonical_name = canonicalize_name(req.name or '')
|
||||
allowed_formats = format_control.get_allowed_formats(canonical_name)
|
||||
return "binary" in allowed_formats
|
||||
return 'binary' in allowed_formats
|
||||
|
||||
return check_binary_allowed
|
||||
|
||||
|
|
@ -86,102 +85,102 @@ class InstallCommand(RequirementCommand):
|
|||
|
||||
self.cmd_opts.add_option(cmdoptions.editable())
|
||||
self.cmd_opts.add_option(
|
||||
"-t",
|
||||
"--target",
|
||||
dest="target_dir",
|
||||
metavar="dir",
|
||||
'-t',
|
||||
'--target',
|
||||
dest='target_dir',
|
||||
metavar='dir',
|
||||
default=None,
|
||||
help=(
|
||||
"Install packages into <dir>. "
|
||||
"By default this will not replace existing files/folders in "
|
||||
"<dir>. Use --upgrade to replace existing packages in <dir> "
|
||||
"with new versions."
|
||||
'Install packages into <dir>. '
|
||||
'By default this will not replace existing files/folders in '
|
||||
'<dir>. Use --upgrade to replace existing packages in <dir> '
|
||||
'with new versions.'
|
||||
),
|
||||
)
|
||||
cmdoptions.add_target_python_options(self.cmd_opts)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--user",
|
||||
dest="use_user_site",
|
||||
action="store_true",
|
||||
'--user',
|
||||
dest='use_user_site',
|
||||
action='store_true',
|
||||
help=(
|
||||
"Install to the Python user install directory for your "
|
||||
"platform. Typically ~/.local/, or %APPDATA%\\Python on "
|
||||
"Windows. (See the Python documentation for site.USER_BASE "
|
||||
"for full details.)"
|
||||
'Install to the Python user install directory for your '
|
||||
'platform. Typically ~/.local/, or %APPDATA%\\Python on '
|
||||
'Windows. (See the Python documentation for site.USER_BASE '
|
||||
'for full details.)'
|
||||
),
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"--no-user",
|
||||
dest="use_user_site",
|
||||
action="store_false",
|
||||
'--no-user',
|
||||
dest='use_user_site',
|
||||
action='store_false',
|
||||
help=SUPPRESS_HELP,
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"--root",
|
||||
dest="root_path",
|
||||
metavar="dir",
|
||||
'--root',
|
||||
dest='root_path',
|
||||
metavar='dir',
|
||||
default=None,
|
||||
help="Install everything relative to this alternate root directory.",
|
||||
help='Install everything relative to this alternate root directory.',
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"--prefix",
|
||||
dest="prefix_path",
|
||||
metavar="dir",
|
||||
'--prefix',
|
||||
dest='prefix_path',
|
||||
metavar='dir',
|
||||
default=None,
|
||||
help=(
|
||||
"Installation prefix where lib, bin and other top-level "
|
||||
"folders are placed"
|
||||
'Installation prefix where lib, bin and other top-level '
|
||||
'folders are placed'
|
||||
),
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.src())
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"-U",
|
||||
"--upgrade",
|
||||
dest="upgrade",
|
||||
action="store_true",
|
||||
'-U',
|
||||
'--upgrade',
|
||||
dest='upgrade',
|
||||
action='store_true',
|
||||
help=(
|
||||
"Upgrade all specified packages to the newest available "
|
||||
"version. The handling of dependencies depends on the "
|
||||
"upgrade-strategy used."
|
||||
'Upgrade all specified packages to the newest available '
|
||||
'version. The handling of dependencies depends on the '
|
||||
'upgrade-strategy used.'
|
||||
),
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--upgrade-strategy",
|
||||
dest="upgrade_strategy",
|
||||
default="only-if-needed",
|
||||
choices=["only-if-needed", "eager"],
|
||||
'--upgrade-strategy',
|
||||
dest='upgrade_strategy',
|
||||
default='only-if-needed',
|
||||
choices=['only-if-needed', 'eager'],
|
||||
help=(
|
||||
"Determines how dependency upgrading should be handled "
|
||||
"[default: %default]. "
|
||||
'Determines how dependency upgrading should be handled '
|
||||
'[default: %default]. '
|
||||
'"eager" - dependencies are upgraded regardless of '
|
||||
"whether the currently installed version satisfies the "
|
||||
"requirements of the upgraded package(s). "
|
||||
'whether the currently installed version satisfies the '
|
||||
'requirements of the upgraded package(s). '
|
||||
'"only-if-needed" - are upgraded only when they do not '
|
||||
"satisfy the requirements of the upgraded package(s)."
|
||||
'satisfy the requirements of the upgraded package(s).'
|
||||
),
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--force-reinstall",
|
||||
dest="force_reinstall",
|
||||
action="store_true",
|
||||
help="Reinstall all packages even if they are already up-to-date.",
|
||||
'--force-reinstall',
|
||||
dest='force_reinstall',
|
||||
action='store_true',
|
||||
help='Reinstall all packages even if they are already up-to-date.',
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"-I",
|
||||
"--ignore-installed",
|
||||
dest="ignore_installed",
|
||||
action="store_true",
|
||||
'-I',
|
||||
'--ignore-installed',
|
||||
dest='ignore_installed',
|
||||
action='store_true',
|
||||
help=(
|
||||
"Ignore the installed packages, overwriting them. "
|
||||
"This can break your system if the existing package "
|
||||
"is of a different version or was installed "
|
||||
"with a different package manager!"
|
||||
'Ignore the installed packages, overwriting them. '
|
||||
'This can break your system if the existing package '
|
||||
'is of a different version or was installed '
|
||||
'with a different package manager!'
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -194,33 +193,33 @@ class InstallCommand(RequirementCommand):
|
|||
self.cmd_opts.add_option(cmdoptions.global_options())
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--compile",
|
||||
action="store_true",
|
||||
dest="compile",
|
||||
'--compile',
|
||||
action='store_true',
|
||||
dest='compile',
|
||||
default=True,
|
||||
help="Compile Python source files to bytecode",
|
||||
help='Compile Python source files to bytecode',
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--no-compile",
|
||||
action="store_false",
|
||||
dest="compile",
|
||||
help="Do not compile Python source files to bytecode",
|
||||
'--no-compile',
|
||||
action='store_false',
|
||||
dest='compile',
|
||||
help='Do not compile Python source files to bytecode',
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--no-warn-script-location",
|
||||
action="store_false",
|
||||
dest="warn_script_location",
|
||||
'--no-warn-script-location',
|
||||
action='store_false',
|
||||
dest='warn_script_location',
|
||||
default=True,
|
||||
help="Do not warn when installing scripts outside PATH",
|
||||
help='Do not warn when installing scripts outside PATH',
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"--no-warn-conflicts",
|
||||
action="store_false",
|
||||
dest="warn_about_conflicts",
|
||||
'--no-warn-conflicts',
|
||||
action='store_false',
|
||||
dest='warn_about_conflicts',
|
||||
default=True,
|
||||
help="Do not warn about broken dependencies",
|
||||
help='Do not warn about broken dependencies',
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(cmdoptions.no_binary())
|
||||
|
|
@ -238,12 +237,12 @@ class InstallCommand(RequirementCommand):
|
|||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
@with_cleanup
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
if options.use_user_site and options.target_dir is not None:
|
||||
raise CommandError("Can not combine '--user' and '--target'")
|
||||
|
||||
cmdoptions.check_install_build_global(options)
|
||||
upgrade_strategy = "to-satisfy-only"
|
||||
upgrade_strategy = 'to-satisfy-only'
|
||||
if options.upgrade:
|
||||
upgrade_strategy = options.upgrade_strategy
|
||||
|
||||
|
|
@ -251,7 +250,7 @@ class InstallCommand(RequirementCommand):
|
|||
|
||||
install_options = options.install_options or []
|
||||
|
||||
logger.verbose("Using %s", get_pip_version())
|
||||
logger.verbose('Using %s', get_pip_version())
|
||||
options.use_user_site = decide_user_install(
|
||||
options.use_user_site,
|
||||
prefix_path=options.prefix_path,
|
||||
|
|
@ -260,8 +259,8 @@ class InstallCommand(RequirementCommand):
|
|||
isolated_mode=options.isolated_mode,
|
||||
)
|
||||
|
||||
target_temp_dir: Optional[TempDirectory] = None
|
||||
target_temp_dir_path: Optional[str] = None
|
||||
target_temp_dir: TempDirectory | None = None
|
||||
target_temp_dir_path: str | None = None
|
||||
if options.target_dir:
|
||||
options.ignore_installed = True
|
||||
options.target_dir = os.path.abspath(options.target_dir)
|
||||
|
|
@ -272,11 +271,11 @@ class InstallCommand(RequirementCommand):
|
|||
# fmt: on
|
||||
):
|
||||
raise CommandError(
|
||||
"Target path exists but is not a directory, will not continue."
|
||||
'Target path exists but is not a directory, will not continue.',
|
||||
)
|
||||
|
||||
# Create a target directory for using with the target option
|
||||
target_temp_dir = TempDirectory(kind="target")
|
||||
target_temp_dir = TempDirectory(kind='target')
|
||||
target_temp_dir_path = target_temp_dir.path
|
||||
self.enter_context(target_temp_dir)
|
||||
|
||||
|
|
@ -297,7 +296,7 @@ class InstallCommand(RequirementCommand):
|
|||
|
||||
directory = TempDirectory(
|
||||
delete=not options.no_clean,
|
||||
kind="install",
|
||||
kind='install',
|
||||
globally_managed=True,
|
||||
)
|
||||
|
||||
|
|
@ -337,11 +336,11 @@ class InstallCommand(RequirementCommand):
|
|||
self.trace_basic_info(finder)
|
||||
|
||||
requirement_set = resolver.resolve(
|
||||
reqs, check_supported_wheels=not options.target_dir
|
||||
reqs, check_supported_wheels=not options.target_dir,
|
||||
)
|
||||
|
||||
try:
|
||||
pip_req = requirement_set.get_requirement("pip")
|
||||
pip_req = requirement_set.get_requirement('pip')
|
||||
except KeyError:
|
||||
modifying_pip = False
|
||||
else:
|
||||
|
|
@ -368,15 +367,15 @@ class InstallCommand(RequirementCommand):
|
|||
|
||||
# If we're using PEP 517, we cannot do a legacy setup.py install
|
||||
# so we fail here.
|
||||
pep517_build_failure_names: List[str] = [
|
||||
pep517_build_failure_names: list[str] = [
|
||||
r.name for r in build_failures if r.use_pep517 # type: ignore
|
||||
]
|
||||
if pep517_build_failure_names:
|
||||
raise InstallationError(
|
||||
"Could not build wheels for {}, which is required to "
|
||||
"install pyproject.toml-based projects".format(
|
||||
", ".join(pep517_build_failure_names)
|
||||
)
|
||||
'Could not build wheels for {}, which is required to '
|
||||
'install pyproject.toml-based projects'.format(
|
||||
', '.join(pep517_build_failure_names),
|
||||
),
|
||||
)
|
||||
|
||||
# For now, we just warn about failures building legacy
|
||||
|
|
@ -389,7 +388,7 @@ class InstallCommand(RequirementCommand):
|
|||
to_install = resolver.get_installation_order(requirement_set)
|
||||
|
||||
# Check for conflicts in the package set we're installing.
|
||||
conflicts: Optional[ConflictDetails] = None
|
||||
conflicts: ConflictDetails | None = None
|
||||
should_warn_about_conflicts = (
|
||||
not options.ignore_dependencies and options.warn_about_conflicts
|
||||
)
|
||||
|
|
@ -423,14 +422,14 @@ class InstallCommand(RequirementCommand):
|
|||
)
|
||||
env = get_environment(lib_locations)
|
||||
|
||||
installed.sort(key=operator.attrgetter("name"))
|
||||
installed.sort(key=operator.attrgetter('name'))
|
||||
items = []
|
||||
for result in installed:
|
||||
item = result.name
|
||||
try:
|
||||
installed_dist = env.get_distribution(item)
|
||||
if installed_dist is not None:
|
||||
item = f"{item}-{installed_dist.version}"
|
||||
item = f'{item}-{installed_dist.version}'
|
||||
except Exception:
|
||||
pass
|
||||
items.append(item)
|
||||
|
|
@ -441,10 +440,10 @@ class InstallCommand(RequirementCommand):
|
|||
resolver_variant=self.determine_resolver_variant(options),
|
||||
)
|
||||
|
||||
installed_desc = " ".join(items)
|
||||
installed_desc = ' '.join(items)
|
||||
if installed_desc:
|
||||
write_output(
|
||||
"Successfully installed %s",
|
||||
'Successfully installed %s',
|
||||
installed_desc,
|
||||
)
|
||||
except OSError as error:
|
||||
|
|
@ -462,14 +461,14 @@ class InstallCommand(RequirementCommand):
|
|||
if options.target_dir:
|
||||
assert target_temp_dir
|
||||
self._handle_target_dir(
|
||||
options.target_dir, target_temp_dir, options.upgrade
|
||||
options.target_dir, target_temp_dir, options.upgrade,
|
||||
)
|
||||
|
||||
warn_if_run_as_root()
|
||||
return SUCCESS
|
||||
|
||||
def _handle_target_dir(
|
||||
self, target_dir: str, target_temp_dir: TempDirectory, upgrade: bool
|
||||
self, target_dir: str, target_temp_dir: TempDirectory, upgrade: bool,
|
||||
) -> None:
|
||||
ensure_dir(target_dir)
|
||||
|
||||
|
|
@ -479,7 +478,7 @@ class InstallCommand(RequirementCommand):
|
|||
|
||||
# Checking both purelib and platlib directories for installed
|
||||
# packages to be moved to target directory
|
||||
scheme = get_scheme("", home=target_temp_dir.path)
|
||||
scheme = get_scheme('', home=target_temp_dir.path)
|
||||
purelib_dir = scheme.purelib
|
||||
platlib_dir = scheme.platlib
|
||||
data_dir = scheme.data
|
||||
|
|
@ -501,17 +500,17 @@ class InstallCommand(RequirementCommand):
|
|||
if os.path.exists(target_item_dir):
|
||||
if not upgrade:
|
||||
logger.warning(
|
||||
"Target directory %s already exists. Specify "
|
||||
"--upgrade to force replacement.",
|
||||
'Target directory %s already exists. Specify '
|
||||
'--upgrade to force replacement.',
|
||||
target_item_dir,
|
||||
)
|
||||
continue
|
||||
if os.path.islink(target_item_dir):
|
||||
logger.warning(
|
||||
"Target directory %s already exists and is "
|
||||
"a link. pip will not automatically replace "
|
||||
"links, please remove if replacement is "
|
||||
"desired.",
|
||||
'Target directory %s already exists and is '
|
||||
'a link. pip will not automatically replace '
|
||||
'links, please remove if replacement is '
|
||||
'desired.',
|
||||
target_item_dir,
|
||||
)
|
||||
continue
|
||||
|
|
@ -523,37 +522,37 @@ class InstallCommand(RequirementCommand):
|
|||
shutil.move(os.path.join(lib_dir, item), target_item_dir)
|
||||
|
||||
def _determine_conflicts(
|
||||
self, to_install: List[InstallRequirement]
|
||||
) -> Optional[ConflictDetails]:
|
||||
self, to_install: list[InstallRequirement],
|
||||
) -> ConflictDetails | None:
|
||||
try:
|
||||
return check_install_conflicts(to_install)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Error while checking for conflicts. Please file an issue on "
|
||||
"pip's issue tracker: https://github.com/pypa/pip/issues/new"
|
||||
'Error while checking for conflicts. Please file an issue on '
|
||||
"pip's issue tracker: https://github.com/pypa/pip/issues/new",
|
||||
)
|
||||
return None
|
||||
|
||||
def _warn_about_conflicts(
|
||||
self, conflict_details: ConflictDetails, resolver_variant: str
|
||||
self, conflict_details: ConflictDetails, resolver_variant: str,
|
||||
) -> None:
|
||||
package_set, (missing, conflicting) = conflict_details
|
||||
if not missing and not conflicting:
|
||||
return
|
||||
|
||||
parts: List[str] = []
|
||||
if resolver_variant == "legacy":
|
||||
parts: list[str] = []
|
||||
if resolver_variant == 'legacy':
|
||||
parts.append(
|
||||
"pip's legacy dependency resolver does not consider dependency "
|
||||
"conflicts when selecting packages. This behaviour is the "
|
||||
"source of the following dependency conflicts."
|
||||
'conflicts when selecting packages. This behaviour is the '
|
||||
'source of the following dependency conflicts.',
|
||||
)
|
||||
else:
|
||||
assert resolver_variant == "2020-resolver"
|
||||
assert resolver_variant == '2020-resolver'
|
||||
parts.append(
|
||||
"pip's dependency resolver does not currently take into account "
|
||||
"all the packages that are installed. This behaviour is the "
|
||||
"source of the following dependency conflicts."
|
||||
'all the packages that are installed. This behaviour is the '
|
||||
'source of the following dependency conflicts.',
|
||||
)
|
||||
|
||||
# NOTE: There is some duplication here, with commands/check.py
|
||||
|
|
@ -561,8 +560,8 @@ class InstallCommand(RequirementCommand):
|
|||
version = package_set[project_name][0]
|
||||
for dependency in missing[project_name]:
|
||||
message = (
|
||||
"{name} {version} requires {requirement}, "
|
||||
"which is not installed."
|
||||
'{name} {version} requires {requirement}, '
|
||||
'which is not installed.'
|
||||
).format(
|
||||
name=project_name,
|
||||
version=version,
|
||||
|
|
@ -574,30 +573,30 @@ class InstallCommand(RequirementCommand):
|
|||
version = package_set[project_name][0]
|
||||
for dep_name, dep_version, req in conflicting[project_name]:
|
||||
message = (
|
||||
"{name} {version} requires {requirement}, but {you} have "
|
||||
"{dep_name} {dep_version} which is incompatible."
|
||||
'{name} {version} requires {requirement}, but {you} have '
|
||||
'{dep_name} {dep_version} which is incompatible.'
|
||||
).format(
|
||||
name=project_name,
|
||||
version=version,
|
||||
requirement=req,
|
||||
dep_name=dep_name,
|
||||
dep_version=dep_version,
|
||||
you=("you" if resolver_variant == "2020-resolver" else "you'll"),
|
||||
you=('you' if resolver_variant == '2020-resolver' else "you'll"),
|
||||
)
|
||||
parts.append(message)
|
||||
|
||||
logger.critical("\n".join(parts))
|
||||
logger.critical('\n'.join(parts))
|
||||
|
||||
|
||||
def get_lib_location_guesses(
|
||||
user: bool = False,
|
||||
home: Optional[str] = None,
|
||||
root: Optional[str] = None,
|
||||
home: str | None = None,
|
||||
root: str | None = None,
|
||||
isolated: bool = False,
|
||||
prefix: Optional[str] = None,
|
||||
) -> List[str]:
|
||||
prefix: str | None = None,
|
||||
) -> list[str]:
|
||||
scheme = get_scheme(
|
||||
"",
|
||||
'',
|
||||
user=user,
|
||||
home=home,
|
||||
root=root,
|
||||
|
|
@ -607,7 +606,7 @@ def get_lib_location_guesses(
|
|||
return [scheme.purelib, scheme.platlib]
|
||||
|
||||
|
||||
def site_packages_writable(root: Optional[str], isolated: bool) -> bool:
|
||||
def site_packages_writable(root: str | None, isolated: bool) -> bool:
|
||||
return all(
|
||||
test_writable_dir(d)
|
||||
for d in set(get_lib_location_guesses(root=root, isolated=isolated))
|
||||
|
|
@ -615,10 +614,10 @@ def site_packages_writable(root: Optional[str], isolated: bool) -> bool:
|
|||
|
||||
|
||||
def decide_user_install(
|
||||
use_user_site: Optional[bool],
|
||||
prefix_path: Optional[str] = None,
|
||||
target_dir: Optional[str] = None,
|
||||
root_path: Optional[str] = None,
|
||||
use_user_site: bool | None,
|
||||
prefix_path: str | None = None,
|
||||
target_dir: str | None = None,
|
||||
root_path: str | None = None,
|
||||
isolated_mode: bool = False,
|
||||
) -> bool:
|
||||
"""Determine whether to do a user install based on the input options.
|
||||
|
|
@ -632,21 +631,21 @@ def decide_user_install(
|
|||
# In some cases (config from tox), use_user_site can be set to an integer
|
||||
# rather than a bool, which 'use_user_site is False' wouldn't catch.
|
||||
if (use_user_site is not None) and (not use_user_site):
|
||||
logger.debug("Non-user install by explicit request")
|
||||
logger.debug('Non-user install by explicit request')
|
||||
return False
|
||||
|
||||
if use_user_site:
|
||||
if prefix_path:
|
||||
raise CommandError(
|
||||
"Can not combine '--user' and '--prefix' as they imply "
|
||||
"different installation locations"
|
||||
'different installation locations',
|
||||
)
|
||||
if virtualenv_no_global():
|
||||
raise InstallationError(
|
||||
"Can not perform a '--user' install. User site-packages "
|
||||
"are not visible in this virtualenv."
|
||||
'are not visible in this virtualenv.',
|
||||
)
|
||||
logger.debug("User install by explicit request")
|
||||
logger.debug('User install by explicit request')
|
||||
return True
|
||||
|
||||
# If we are here, user installs have not been explicitly requested/avoided
|
||||
|
|
@ -654,36 +653,36 @@ def decide_user_install(
|
|||
|
||||
# user install incompatible with --prefix/--target
|
||||
if prefix_path or target_dir:
|
||||
logger.debug("Non-user install due to --prefix or --target option")
|
||||
logger.debug('Non-user install due to --prefix or --target option')
|
||||
return False
|
||||
|
||||
# If user installs are not enabled, choose a non-user install
|
||||
if not site.ENABLE_USER_SITE:
|
||||
logger.debug("Non-user install because user site-packages disabled")
|
||||
logger.debug('Non-user install because user site-packages disabled')
|
||||
return False
|
||||
|
||||
# If we have permission for a non-user install, do that,
|
||||
# otherwise do a user install.
|
||||
if site_packages_writable(root=root_path, isolated=isolated_mode):
|
||||
logger.debug("Non-user install because site-packages writeable")
|
||||
logger.debug('Non-user install because site-packages writeable')
|
||||
return False
|
||||
|
||||
logger.info(
|
||||
"Defaulting to user installation because normal site-packages "
|
||||
"is not writeable"
|
||||
'Defaulting to user installation because normal site-packages '
|
||||
'is not writeable',
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def reject_location_related_install_options(
|
||||
requirements: List[InstallRequirement], options: Optional[List[str]]
|
||||
requirements: list[InstallRequirement], options: list[str] | None,
|
||||
) -> None:
|
||||
"""If any location-changing --install-option arguments were passed for
|
||||
requirements or on the command-line, then show a deprecation warning.
|
||||
"""
|
||||
|
||||
def format_options(option_names: Iterable[str]) -> List[str]:
|
||||
return ["--{}".format(name.replace("_", "-")) for name in option_names]
|
||||
def format_options(option_names: Iterable[str]) -> list[str]:
|
||||
return ['--{}'.format(name.replace('_', '-')) for name in option_names]
|
||||
|
||||
offenders = []
|
||||
|
||||
|
|
@ -692,30 +691,30 @@ def reject_location_related_install_options(
|
|||
location_options = parse_distutils_args(install_options)
|
||||
if location_options:
|
||||
offenders.append(
|
||||
"{!r} from {}".format(
|
||||
format_options(location_options.keys()), requirement
|
||||
)
|
||||
'{!r} from {}'.format(
|
||||
format_options(location_options.keys()), requirement,
|
||||
),
|
||||
)
|
||||
|
||||
if options:
|
||||
location_options = parse_distutils_args(options)
|
||||
if location_options:
|
||||
offenders.append(
|
||||
"{!r} from command line".format(format_options(location_options.keys()))
|
||||
f'{format_options(location_options.keys())!r} from command line',
|
||||
)
|
||||
|
||||
if not offenders:
|
||||
return
|
||||
|
||||
raise CommandError(
|
||||
"Location-changing options found in --install-option: {}."
|
||||
" This is unsupported, use pip-level options like --user,"
|
||||
" --prefix, --root, and --target instead.".format("; ".join(offenders))
|
||||
'Location-changing options found in --install-option: {}.'
|
||||
' This is unsupported, use pip-level options like --user,'
|
||||
' --prefix, --root, and --target instead.'.format('; '.join(offenders)),
|
||||
)
|
||||
|
||||
|
||||
def create_os_error_message(
|
||||
error: OSError, show_traceback: bool, using_user_site: bool
|
||||
error: OSError, show_traceback: bool, using_user_site: bool,
|
||||
) -> str:
|
||||
"""Format an error message for an OSError
|
||||
|
||||
|
|
@ -724,48 +723,48 @@ def create_os_error_message(
|
|||
parts = []
|
||||
|
||||
# Mention the error if we are not going to show a traceback
|
||||
parts.append("Could not install packages due to an OSError")
|
||||
parts.append('Could not install packages due to an OSError')
|
||||
if not show_traceback:
|
||||
parts.append(": ")
|
||||
parts.append(': ')
|
||||
parts.append(str(error))
|
||||
else:
|
||||
parts.append(".")
|
||||
parts.append('.')
|
||||
|
||||
# Spilt the error indication from a helper message (if any)
|
||||
parts[-1] += "\n"
|
||||
parts[-1] += '\n'
|
||||
|
||||
# Suggest useful actions to the user:
|
||||
# (1) using user site-packages or (2) verifying the permissions
|
||||
if error.errno == errno.EACCES:
|
||||
user_option_part = "Consider using the `--user` option"
|
||||
permissions_part = "Check the permissions"
|
||||
user_option_part = 'Consider using the `--user` option'
|
||||
permissions_part = 'Check the permissions'
|
||||
|
||||
if not running_under_virtualenv() and not using_user_site:
|
||||
parts.extend(
|
||||
[
|
||||
user_option_part,
|
||||
" or ",
|
||||
' or ',
|
||||
permissions_part.lower(),
|
||||
]
|
||||
],
|
||||
)
|
||||
else:
|
||||
parts.append(permissions_part)
|
||||
parts.append(".\n")
|
||||
parts.append('.\n')
|
||||
|
||||
# Suggest the user to enable Long Paths if path length is
|
||||
# more than 260
|
||||
if (
|
||||
WINDOWS
|
||||
and error.errno == errno.ENOENT
|
||||
and error.filename
|
||||
and len(error.filename) > 260
|
||||
WINDOWS and
|
||||
error.errno == errno.ENOENT and
|
||||
error.filename and
|
||||
len(error.filename) > 260
|
||||
):
|
||||
parts.append(
|
||||
"HINT: This error might have occurred since "
|
||||
"this system does not have Windows Long Path "
|
||||
"support enabled. You can find information on "
|
||||
"how to enable this at "
|
||||
"https://pip.pypa.io/warnings/enable-long-paths\n"
|
||||
'HINT: This error might have occurred since '
|
||||
'this system does not have Windows Long Path '
|
||||
'support enabled. You can find information on '
|
||||
'how to enable this at '
|
||||
'https://pip.pypa.io/warnings/enable-long-paths\n',
|
||||
)
|
||||
|
||||
return "".join(parts).strip() + "\n"
|
||||
return ''.join(parts).strip() + '\n'
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from optparse import Values
|
||||
from typing import TYPE_CHECKING, Iterator, List, Optional, Sequence, Tuple, cast
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from typing import cast
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.req_command import IndexGroupCommand
|
||||
|
|
@ -11,11 +17,14 @@ from pip._internal.cli.status_codes import SUCCESS
|
|||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.index.collector import LinkCollector
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import BaseDistribution, get_environment
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.metadata import get_environment
|
||||
from pip._internal.models.selection_prefs import SelectionPreferences
|
||||
from pip._internal.network.session import PipSession
|
||||
from pip._internal.utils.compat import stdlib_pkgs
|
||||
from pip._internal.utils.misc import tabulate, write_output
|
||||
from pip._internal.utils.misc import tabulate
|
||||
from pip._internal.utils.misc import write_output
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._internal.metadata.base import DistributionVersion
|
||||
|
|
@ -49,81 +58,81 @@ class ListCommand(IndexGroupCommand):
|
|||
|
||||
def add_options(self) -> None:
|
||||
self.cmd_opts.add_option(
|
||||
"-o",
|
||||
"--outdated",
|
||||
action="store_true",
|
||||
'-o',
|
||||
'--outdated',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="List outdated packages",
|
||||
help='List outdated packages',
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"-u",
|
||||
"--uptodate",
|
||||
action="store_true",
|
||||
'-u',
|
||||
'--uptodate',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="List uptodate packages",
|
||||
help='List uptodate packages',
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"-e",
|
||||
"--editable",
|
||||
action="store_true",
|
||||
'-e',
|
||||
'--editable',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="List editable projects.",
|
||||
help='List editable projects.',
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"-l",
|
||||
"--local",
|
||||
action="store_true",
|
||||
'-l',
|
||||
'--local',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=(
|
||||
"If in a virtualenv that has global access, do not list "
|
||||
"globally-installed packages."
|
||||
'If in a virtualenv that has global access, do not list '
|
||||
'globally-installed packages.'
|
||||
),
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"--user",
|
||||
dest="user",
|
||||
action="store_true",
|
||||
'--user',
|
||||
dest='user',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Only output packages installed in user-site.",
|
||||
help='Only output packages installed in user-site.',
|
||||
)
|
||||
self.cmd_opts.add_option(cmdoptions.list_path())
|
||||
self.cmd_opts.add_option(
|
||||
"--pre",
|
||||
action="store_true",
|
||||
'--pre',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=(
|
||||
"Include pre-release and development versions. By default, "
|
||||
"pip only finds stable versions."
|
||||
'Include pre-release and development versions. By default, '
|
||||
'pip only finds stable versions.'
|
||||
),
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--format",
|
||||
action="store",
|
||||
dest="list_format",
|
||||
default="columns",
|
||||
choices=("columns", "freeze", "json"),
|
||||
help="Select the output format among: columns (default), freeze, or json",
|
||||
'--format',
|
||||
action='store',
|
||||
dest='list_format',
|
||||
default='columns',
|
||||
choices=('columns', 'freeze', 'json'),
|
||||
help='Select the output format among: columns (default), freeze, or json',
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--not-required",
|
||||
action="store_true",
|
||||
dest="not_required",
|
||||
help="List packages that are not dependencies of installed packages.",
|
||||
'--not-required',
|
||||
action='store_true',
|
||||
dest='not_required',
|
||||
help='List packages that are not dependencies of installed packages.',
|
||||
)
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--exclude-editable",
|
||||
action="store_false",
|
||||
dest="include_editable",
|
||||
help="Exclude editable package from output.",
|
||||
'--exclude-editable',
|
||||
action='store_false',
|
||||
dest='include_editable',
|
||||
help='Exclude editable package from output.',
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"--include-editable",
|
||||
action="store_true",
|
||||
dest="include_editable",
|
||||
help="Include editable package from output.",
|
||||
'--include-editable',
|
||||
action='store_true',
|
||||
dest='include_editable',
|
||||
help='Include editable package from output.',
|
||||
default=True,
|
||||
)
|
||||
self.cmd_opts.add_option(cmdoptions.list_exclude())
|
||||
|
|
@ -133,7 +142,7 @@ class ListCommand(IndexGroupCommand):
|
|||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def _build_package_finder(
|
||||
self, options: Values, session: PipSession
|
||||
self, options: Values, session: PipSession,
|
||||
) -> PackageFinder:
|
||||
"""
|
||||
Create a package finder appropriate to this list command.
|
||||
|
|
@ -149,12 +158,12 @@ class ListCommand(IndexGroupCommand):
|
|||
return PackageFinder.create(
|
||||
link_collector=link_collector,
|
||||
selection_prefs=selection_prefs,
|
||||
use_deprecated_html5lib="html5lib" in options.deprecated_features_enabled,
|
||||
use_deprecated_html5lib='html5lib' in options.deprecated_features_enabled,
|
||||
)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
if options.outdated and options.uptodate:
|
||||
raise CommandError("Options --outdated and --uptodate cannot be combined.")
|
||||
raise CommandError('Options --outdated and --uptodate cannot be combined.')
|
||||
|
||||
cmdoptions.check_list_path_option(options)
|
||||
|
||||
|
|
@ -162,8 +171,8 @@ class ListCommand(IndexGroupCommand):
|
|||
if options.excludes:
|
||||
skip.update(canonicalize_name(n) for n in options.excludes)
|
||||
|
||||
packages: "_ProcessedDists" = [
|
||||
cast("_DistWithLatestInfo", d)
|
||||
packages: _ProcessedDists = [
|
||||
cast('_DistWithLatestInfo', d)
|
||||
for d in get_environment(options.path).iter_installed_distributions(
|
||||
local_only=options.local,
|
||||
user_only=options.user,
|
||||
|
|
@ -189,8 +198,8 @@ class ListCommand(IndexGroupCommand):
|
|||
return SUCCESS
|
||||
|
||||
def get_outdated(
|
||||
self, packages: "_ProcessedDists", options: Values
|
||||
) -> "_ProcessedDists":
|
||||
self, packages: _ProcessedDists, options: Values,
|
||||
) -> _ProcessedDists:
|
||||
return [
|
||||
dist
|
||||
for dist in self.iter_packages_latest_infos(packages, options)
|
||||
|
|
@ -198,8 +207,8 @@ class ListCommand(IndexGroupCommand):
|
|||
]
|
||||
|
||||
def get_uptodate(
|
||||
self, packages: "_ProcessedDists", options: Values
|
||||
) -> "_ProcessedDists":
|
||||
self, packages: _ProcessedDists, options: Values,
|
||||
) -> _ProcessedDists:
|
||||
return [
|
||||
dist
|
||||
for dist in self.iter_packages_latest_infos(packages, options)
|
||||
|
|
@ -207,8 +216,8 @@ class ListCommand(IndexGroupCommand):
|
|||
]
|
||||
|
||||
def get_not_required(
|
||||
self, packages: "_ProcessedDists", options: Values
|
||||
) -> "_ProcessedDists":
|
||||
self, packages: _ProcessedDists, options: Values,
|
||||
) -> _ProcessedDists:
|
||||
dep_keys = {
|
||||
canonicalize_name(dep.name)
|
||||
for dist in packages
|
||||
|
|
@ -221,14 +230,14 @@ class ListCommand(IndexGroupCommand):
|
|||
return list({pkg for pkg in packages if pkg.canonical_name not in dep_keys})
|
||||
|
||||
def iter_packages_latest_infos(
|
||||
self, packages: "_ProcessedDists", options: Values
|
||||
) -> Iterator["_DistWithLatestInfo"]:
|
||||
self, packages: _ProcessedDists, options: Values,
|
||||
) -> Iterator[_DistWithLatestInfo]:
|
||||
with self._build_session(options) as session:
|
||||
finder = self._build_package_finder(options, session)
|
||||
|
||||
def latest_info(
|
||||
dist: "_DistWithLatestInfo",
|
||||
) -> Optional["_DistWithLatestInfo"]:
|
||||
dist: _DistWithLatestInfo,
|
||||
) -> _DistWithLatestInfo | None:
|
||||
all_candidates = finder.find_all_candidates(dist.canonical_name)
|
||||
if not options.pre:
|
||||
# Remove prereleases
|
||||
|
|
@ -247,9 +256,9 @@ class ListCommand(IndexGroupCommand):
|
|||
|
||||
remote_version = best_candidate.version
|
||||
if best_candidate.link.is_wheel:
|
||||
typ = "wheel"
|
||||
typ = 'wheel'
|
||||
else:
|
||||
typ = "sdist"
|
||||
typ = 'sdist'
|
||||
dist.latest_version = remote_version
|
||||
dist.latest_filetype = typ
|
||||
return dist
|
||||
|
|
@ -259,28 +268,28 @@ class ListCommand(IndexGroupCommand):
|
|||
yield dist
|
||||
|
||||
def output_package_listing(
|
||||
self, packages: "_ProcessedDists", options: Values
|
||||
self, packages: _ProcessedDists, options: Values,
|
||||
) -> None:
|
||||
packages = sorted(
|
||||
packages,
|
||||
key=lambda dist: dist.canonical_name,
|
||||
)
|
||||
if options.list_format == "columns" and packages:
|
||||
if options.list_format == 'columns' and packages:
|
||||
data, header = format_for_columns(packages, options)
|
||||
self.output_package_listing_columns(data, header)
|
||||
elif options.list_format == "freeze":
|
||||
elif options.list_format == 'freeze':
|
||||
for dist in packages:
|
||||
if options.verbose >= 1:
|
||||
write_output(
|
||||
"%s==%s (%s)", dist.raw_name, dist.version, dist.location
|
||||
'%s==%s (%s)', dist.raw_name, dist.version, dist.location,
|
||||
)
|
||||
else:
|
||||
write_output("%s==%s", dist.raw_name, dist.version)
|
||||
elif options.list_format == "json":
|
||||
write_output('%s==%s', dist.raw_name, dist.version)
|
||||
elif options.list_format == 'json':
|
||||
write_output(format_for_json(packages, options))
|
||||
|
||||
def output_package_listing_columns(
|
||||
self, data: List[List[str]], header: List[str]
|
||||
self, data: list[list[str]], header: list[str],
|
||||
) -> None:
|
||||
# insert the header first: we need to know the size of column names
|
||||
if len(data) > 0:
|
||||
|
|
@ -290,33 +299,33 @@ class ListCommand(IndexGroupCommand):
|
|||
|
||||
# Create and add a separator.
|
||||
if len(data) > 0:
|
||||
pkg_strings.insert(1, " ".join(map(lambda x: "-" * x, sizes)))
|
||||
pkg_strings.insert(1, ' '.join(map(lambda x: '-' * x, sizes)))
|
||||
|
||||
for val in pkg_strings:
|
||||
write_output(val)
|
||||
|
||||
|
||||
def format_for_columns(
|
||||
pkgs: "_ProcessedDists", options: Values
|
||||
) -> Tuple[List[List[str]], List[str]]:
|
||||
pkgs: _ProcessedDists, options: Values,
|
||||
) -> tuple[list[list[str]], list[str]]:
|
||||
"""
|
||||
Convert the package data into something usable
|
||||
by output_package_listing_columns.
|
||||
"""
|
||||
header = ["Package", "Version"]
|
||||
header = ['Package', 'Version']
|
||||
|
||||
running_outdated = options.outdated
|
||||
if running_outdated:
|
||||
header.extend(["Latest", "Type"])
|
||||
header.extend(['Latest', 'Type'])
|
||||
|
||||
has_editables = any(x.editable for x in pkgs)
|
||||
if has_editables:
|
||||
header.append("Editable project location")
|
||||
header.append('Editable project location')
|
||||
|
||||
if options.verbose >= 1:
|
||||
header.append("Location")
|
||||
header.append('Location')
|
||||
if options.verbose >= 1:
|
||||
header.append("Installer")
|
||||
header.append('Installer')
|
||||
|
||||
data = []
|
||||
for proj in pkgs:
|
||||
|
|
@ -329,10 +338,10 @@ def format_for_columns(
|
|||
row.append(proj.latest_filetype)
|
||||
|
||||
if has_editables:
|
||||
row.append(proj.editable_project_location or "")
|
||||
row.append(proj.editable_project_location or '')
|
||||
|
||||
if options.verbose >= 1:
|
||||
row.append(proj.location or "")
|
||||
row.append(proj.location or '')
|
||||
if options.verbose >= 1:
|
||||
row.append(proj.installer)
|
||||
|
||||
|
|
@ -341,21 +350,21 @@ def format_for_columns(
|
|||
return data, header
|
||||
|
||||
|
||||
def format_for_json(packages: "_ProcessedDists", options: Values) -> str:
|
||||
def format_for_json(packages: _ProcessedDists, options: Values) -> str:
|
||||
data = []
|
||||
for dist in packages:
|
||||
info = {
|
||||
"name": dist.raw_name,
|
||||
"version": str(dist.version),
|
||||
'name': dist.raw_name,
|
||||
'version': str(dist.version),
|
||||
}
|
||||
if options.verbose >= 1:
|
||||
info["location"] = dist.location or ""
|
||||
info["installer"] = dist.installer
|
||||
info['location'] = dist.location or ''
|
||||
info['installer'] = dist.installer
|
||||
if options.outdated:
|
||||
info["latest_version"] = str(dist.latest_version)
|
||||
info["latest_filetype"] = dist.latest_filetype
|
||||
info['latest_version'] = str(dist.latest_version)
|
||||
info['latest_filetype'] = dist.latest_filetype
|
||||
editable_project_location = dist.editable_project_location
|
||||
if editable_project_location:
|
||||
info["editable_project_location"] = editable_project_location
|
||||
info['editable_project_location'] = editable_project_location
|
||||
data.append(info)
|
||||
return json.dumps(data)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import shutil
|
||||
import sys
|
||||
|
|
@ -5,19 +7,22 @@ import textwrap
|
|||
import xmlrpc.client
|
||||
from collections import OrderedDict
|
||||
from optparse import Values
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional
|
||||
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.req_command import SessionCommandMixin
|
||||
from pip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS
|
||||
from pip._internal.cli.status_codes import NO_MATCHES_FOUND
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.metadata import get_default_environment
|
||||
from pip._internal.models.index import PyPI
|
||||
from pip._internal.network.xmlrpc import PipXmlrpcTransport
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import write_output
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import TypedDict
|
||||
|
|
@ -25,7 +30,7 @@ if TYPE_CHECKING:
|
|||
class TransformedHit(TypedDict):
|
||||
name: str
|
||||
summary: str
|
||||
versions: List[str]
|
||||
versions: list[str]
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -40,19 +45,19 @@ class SearchCommand(Command, SessionCommandMixin):
|
|||
|
||||
def add_options(self) -> None:
|
||||
self.cmd_opts.add_option(
|
||||
"-i",
|
||||
"--index",
|
||||
dest="index",
|
||||
metavar="URL",
|
||||
'-i',
|
||||
'--index',
|
||||
dest='index',
|
||||
metavar='URL',
|
||||
default=PyPI.pypi_url,
|
||||
help="Base URL of Python Package Index (default %default)",
|
||||
help='Base URL of Python Package Index (default %default)',
|
||||
)
|
||||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
if not args:
|
||||
raise CommandError("Missing required argument (search query).")
|
||||
raise CommandError('Missing required argument (search query).')
|
||||
query = args
|
||||
pypi_hits = self.search(query, options)
|
||||
hits = transform_hits(pypi_hits)
|
||||
|
|
@ -66,7 +71,7 @@ class SearchCommand(Command, SessionCommandMixin):
|
|||
return SUCCESS
|
||||
return NO_MATCHES_FOUND
|
||||
|
||||
def search(self, query: List[str], options: Values) -> List[Dict[str, str]]:
|
||||
def search(self, query: list[str], options: Values) -> list[dict[str, str]]:
|
||||
index_url = options.index
|
||||
|
||||
session = self.get_default_session(options)
|
||||
|
|
@ -74,9 +79,9 @@ class SearchCommand(Command, SessionCommandMixin):
|
|||
transport = PipXmlrpcTransport(index_url, session)
|
||||
pypi = xmlrpc.client.ServerProxy(index_url, transport)
|
||||
try:
|
||||
hits = pypi.search({"name": query, "summary": query}, "or")
|
||||
hits = pypi.search({'name': query, 'summary': query}, 'or')
|
||||
except xmlrpc.client.Fault as fault:
|
||||
message = "XMLRPC request failed [code: {code}]\n{string}".format(
|
||||
message = 'XMLRPC request failed [code: {code}]\n{string}'.format(
|
||||
code=fault.faultCode,
|
||||
string=fault.faultString,
|
||||
)
|
||||
|
|
@ -85,30 +90,30 @@ class SearchCommand(Command, SessionCommandMixin):
|
|||
return hits
|
||||
|
||||
|
||||
def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]:
|
||||
def transform_hits(hits: list[dict[str, str]]) -> list[TransformedHit]:
|
||||
"""
|
||||
The list from pypi is really a list of versions. We want a list of
|
||||
packages with the list of versions stored inline. This converts the
|
||||
list from pypi into one we can use.
|
||||
"""
|
||||
packages: Dict[str, "TransformedHit"] = OrderedDict()
|
||||
packages: dict[str, TransformedHit] = OrderedDict()
|
||||
for hit in hits:
|
||||
name = hit["name"]
|
||||
summary = hit["summary"]
|
||||
version = hit["version"]
|
||||
name = hit['name']
|
||||
summary = hit['summary']
|
||||
version = hit['version']
|
||||
|
||||
if name not in packages.keys():
|
||||
packages[name] = {
|
||||
"name": name,
|
||||
"summary": summary,
|
||||
"versions": [version],
|
||||
'name': name,
|
||||
'summary': summary,
|
||||
'versions': [version],
|
||||
}
|
||||
else:
|
||||
packages[name]["versions"].append(version)
|
||||
packages[name]['versions'].append(version)
|
||||
|
||||
# if this is the highest version, replace summary and score
|
||||
if version == highest_version(packages[name]["versions"]):
|
||||
packages[name]["summary"] = summary
|
||||
if version == highest_version(packages[name]['versions']):
|
||||
packages[name]['summary'] = summary
|
||||
|
||||
return list(packages.values())
|
||||
|
||||
|
|
@ -119,23 +124,23 @@ def print_dist_installation_info(name: str, latest: str) -> None:
|
|||
if dist is not None:
|
||||
with indent_log():
|
||||
if dist.version == latest:
|
||||
write_output("INSTALLED: %s (latest)", dist.version)
|
||||
write_output('INSTALLED: %s (latest)', dist.version)
|
||||
else:
|
||||
write_output("INSTALLED: %s", dist.version)
|
||||
write_output('INSTALLED: %s', dist.version)
|
||||
if parse_version(latest).pre:
|
||||
write_output(
|
||||
"LATEST: %s (pre-release; install"
|
||||
" with `pip install --pre`)",
|
||||
'LATEST: %s (pre-release; install'
|
||||
' with `pip install --pre`)',
|
||||
latest,
|
||||
)
|
||||
else:
|
||||
write_output("LATEST: %s", latest)
|
||||
write_output('LATEST: %s', latest)
|
||||
|
||||
|
||||
def print_results(
|
||||
hits: List["TransformedHit"],
|
||||
name_column_width: Optional[int] = None,
|
||||
terminal_width: Optional[int] = None,
|
||||
hits: list[TransformedHit],
|
||||
name_column_width: int | None = None,
|
||||
terminal_width: int | None = None,
|
||||
) -> None:
|
||||
if not hits:
|
||||
return
|
||||
|
|
@ -143,26 +148,26 @@ def print_results(
|
|||
name_column_width = (
|
||||
max(
|
||||
[
|
||||
len(hit["name"]) + len(highest_version(hit.get("versions", ["-"])))
|
||||
len(hit['name']) + len(highest_version(hit.get('versions', ['-'])))
|
||||
for hit in hits
|
||||
]
|
||||
)
|
||||
+ 4
|
||||
],
|
||||
) +
|
||||
4
|
||||
)
|
||||
|
||||
for hit in hits:
|
||||
name = hit["name"]
|
||||
summary = hit["summary"] or ""
|
||||
latest = highest_version(hit.get("versions", ["-"]))
|
||||
name = hit['name']
|
||||
summary = hit['summary'] or ''
|
||||
latest = highest_version(hit.get('versions', ['-']))
|
||||
if terminal_width is not None:
|
||||
target_width = terminal_width - name_column_width - 5
|
||||
if target_width > 10:
|
||||
# wrap and indent summary to fit terminal
|
||||
summary_lines = textwrap.wrap(summary, target_width)
|
||||
summary = ("\n" + " " * (name_column_width + 3)).join(summary_lines)
|
||||
summary = ('\n' + ' ' * (name_column_width + 3)).join(summary_lines)
|
||||
|
||||
name_latest = f"{name} ({latest})"
|
||||
line = f"{name_latest:{name_column_width}} - {summary}"
|
||||
name_latest = f'{name} ({latest})'
|
||||
line = f'{name_latest:{name_column_width}} - {summary}'
|
||||
try:
|
||||
write_output(line)
|
||||
print_dist_installation_info(name, latest)
|
||||
|
|
@ -170,5 +175,5 @@ def print_results(
|
|||
pass
|
||||
|
||||
|
||||
def highest_version(versions: List[str]) -> str:
|
||||
def highest_version(versions: list[str]) -> str:
|
||||
return max(versions, key=parse_version)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from optparse import Values
|
||||
from typing import Iterator, List, NamedTuple, Optional
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.status_codes import ERROR, SUCCESS
|
||||
from pip._internal.metadata import BaseDistribution, get_default_environment
|
||||
from pip._internal.cli.status_codes import ERROR
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.metadata import get_default_environment
|
||||
from pip._internal.utils.misc import write_output
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -25,25 +31,25 @@ class ShowCommand(Command):
|
|||
|
||||
def add_options(self) -> None:
|
||||
self.cmd_opts.add_option(
|
||||
"-f",
|
||||
"--files",
|
||||
dest="files",
|
||||
action="store_true",
|
||||
'-f',
|
||||
'--files',
|
||||
dest='files',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Show the full list of installed files for each package.",
|
||||
help='Show the full list of installed files for each package.',
|
||||
)
|
||||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
if not args:
|
||||
logger.warning("ERROR: Please provide a package name or names.")
|
||||
logger.warning('ERROR: Please provide a package name or names.')
|
||||
return ERROR
|
||||
query = args
|
||||
|
||||
results = search_packages_info(query)
|
||||
if not print_results(
|
||||
results, list_files=options.files, verbose=options.verbose
|
||||
results, list_files=options.files, verbose=options.verbose,
|
||||
):
|
||||
return ERROR
|
||||
return SUCCESS
|
||||
|
|
@ -53,21 +59,21 @@ class _PackageInfo(NamedTuple):
|
|||
name: str
|
||||
version: str
|
||||
location: str
|
||||
requires: List[str]
|
||||
required_by: List[str]
|
||||
requires: list[str]
|
||||
required_by: list[str]
|
||||
installer: str
|
||||
metadata_version: str
|
||||
classifiers: List[str]
|
||||
classifiers: list[str]
|
||||
summary: str
|
||||
homepage: str
|
||||
author: str
|
||||
author_email: str
|
||||
license: str
|
||||
entry_points: List[str]
|
||||
files: Optional[List[str]]
|
||||
entry_points: list[str]
|
||||
files: list[str] | None
|
||||
|
||||
|
||||
def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
|
||||
def search_packages_info(query: list[str]) -> Iterator[_PackageInfo]:
|
||||
"""
|
||||
Gather details from installed distributions. Print distribution name,
|
||||
version, location, and installed files. Installed files requires a
|
||||
|
|
@ -79,14 +85,14 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
|
|||
installed = {dist.canonical_name: dist for dist in env.iter_distributions()}
|
||||
query_names = [canonicalize_name(name) for name in query]
|
||||
missing = sorted(
|
||||
[name for name, pkg in zip(query, query_names) if pkg not in installed]
|
||||
[name for name, pkg in zip(query, query_names) if pkg not in installed],
|
||||
)
|
||||
if missing:
|
||||
logger.warning("Package(s) not found: %s", ", ".join(missing))
|
||||
logger.warning('Package(s) not found: %s', ', '.join(missing))
|
||||
|
||||
def _get_requiring_packages(current_dist: BaseDistribution) -> Iterator[str]:
|
||||
return (
|
||||
dist.metadata["Name"] or "UNKNOWN"
|
||||
dist.metadata['Name'] or 'UNKNOWN'
|
||||
for dist in installed.values()
|
||||
if current_dist.canonical_name
|
||||
in {canonicalize_name(d.name) for d in dist.iter_dependencies()}
|
||||
|
|
@ -102,14 +108,14 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
|
|||
required_by = sorted(_get_requiring_packages(dist), key=str.lower)
|
||||
|
||||
try:
|
||||
entry_points_text = dist.read_text("entry_points.txt")
|
||||
entry_points_text = dist.read_text('entry_points.txt')
|
||||
entry_points = entry_points_text.splitlines(keepends=False)
|
||||
except FileNotFoundError:
|
||||
entry_points = []
|
||||
|
||||
files_iter = dist.iter_declared_entries()
|
||||
if files_iter is None:
|
||||
files: Optional[List[str]] = None
|
||||
files: list[str] | None = None
|
||||
else:
|
||||
files = sorted(files_iter)
|
||||
|
||||
|
|
@ -118,17 +124,17 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
|
|||
yield _PackageInfo(
|
||||
name=dist.raw_name,
|
||||
version=str(dist.version),
|
||||
location=dist.location or "",
|
||||
location=dist.location or '',
|
||||
requires=requires,
|
||||
required_by=required_by,
|
||||
installer=dist.installer,
|
||||
metadata_version=dist.metadata_version or "",
|
||||
classifiers=metadata.get_all("Classifier", []),
|
||||
summary=metadata.get("Summary", ""),
|
||||
homepage=metadata.get("Home-page", ""),
|
||||
author=metadata.get("Author", ""),
|
||||
author_email=metadata.get("Author-email", ""),
|
||||
license=metadata.get("License", ""),
|
||||
metadata_version=dist.metadata_version or '',
|
||||
classifiers=metadata.get_all('Classifier', []),
|
||||
summary=metadata.get('Summary', ''),
|
||||
homepage=metadata.get('Home-page', ''),
|
||||
author=metadata.get('Author', ''),
|
||||
author_email=metadata.get('Author-email', ''),
|
||||
license=metadata.get('License', ''),
|
||||
entry_points=entry_points,
|
||||
files=files,
|
||||
)
|
||||
|
|
@ -146,33 +152,33 @@ def print_results(
|
|||
for i, dist in enumerate(distributions):
|
||||
results_printed = True
|
||||
if i > 0:
|
||||
write_output("---")
|
||||
write_output('---')
|
||||
|
||||
write_output("Name: %s", dist.name)
|
||||
write_output("Version: %s", dist.version)
|
||||
write_output("Summary: %s", dist.summary)
|
||||
write_output("Home-page: %s", dist.homepage)
|
||||
write_output("Author: %s", dist.author)
|
||||
write_output("Author-email: %s", dist.author_email)
|
||||
write_output("License: %s", dist.license)
|
||||
write_output("Location: %s", dist.location)
|
||||
write_output("Requires: %s", ", ".join(dist.requires))
|
||||
write_output("Required-by: %s", ", ".join(dist.required_by))
|
||||
write_output('Name: %s', dist.name)
|
||||
write_output('Version: %s', dist.version)
|
||||
write_output('Summary: %s', dist.summary)
|
||||
write_output('Home-page: %s', dist.homepage)
|
||||
write_output('Author: %s', dist.author)
|
||||
write_output('Author-email: %s', dist.author_email)
|
||||
write_output('License: %s', dist.license)
|
||||
write_output('Location: %s', dist.location)
|
||||
write_output('Requires: %s', ', '.join(dist.requires))
|
||||
write_output('Required-by: %s', ', '.join(dist.required_by))
|
||||
|
||||
if verbose:
|
||||
write_output("Metadata-Version: %s", dist.metadata_version)
|
||||
write_output("Installer: %s", dist.installer)
|
||||
write_output("Classifiers:")
|
||||
write_output('Metadata-Version: %s', dist.metadata_version)
|
||||
write_output('Installer: %s', dist.installer)
|
||||
write_output('Classifiers:')
|
||||
for classifier in dist.classifiers:
|
||||
write_output(" %s", classifier)
|
||||
write_output("Entry-points:")
|
||||
write_output(' %s', classifier)
|
||||
write_output('Entry-points:')
|
||||
for entry in dist.entry_points:
|
||||
write_output(" %s", entry.strip())
|
||||
write_output(' %s', entry.strip())
|
||||
if list_files:
|
||||
write_output("Files:")
|
||||
write_output('Files:')
|
||||
if dist.files is None:
|
||||
write_output("Cannot locate RECORD or installed-files.txt")
|
||||
write_output('Cannot locate RECORD or installed-files.txt')
|
||||
else:
|
||||
for line in dist.files:
|
||||
write_output(" %s", line.strip())
|
||||
write_output(' %s', line.strip())
|
||||
return results_printed
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from optparse import Values
|
||||
from typing import List
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.req_command import SessionCommandMixin, warn_if_run_as_root
|
||||
from pip._internal.cli.req_command import SessionCommandMixin
|
||||
from pip._internal.cli.req_command import warn_if_run_as_root
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.req import parse_requirements
|
||||
from pip._internal.req.constructors import (
|
||||
install_req_from_line,
|
||||
install_req_from_parsed_requirement,
|
||||
)
|
||||
from pip._internal.req.constructors import install_req_from_line
|
||||
from pip._internal.req.constructors import install_req_from_parsed_requirement
|
||||
from pip._internal.utils.misc import protect_pip_from_modification_on_windows
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -35,28 +35,28 @@ class UninstallCommand(Command, SessionCommandMixin):
|
|||
|
||||
def add_options(self) -> None:
|
||||
self.cmd_opts.add_option(
|
||||
"-r",
|
||||
"--requirement",
|
||||
dest="requirements",
|
||||
action="append",
|
||||
'-r',
|
||||
'--requirement',
|
||||
dest='requirements',
|
||||
action='append',
|
||||
default=[],
|
||||
metavar="file",
|
||||
metavar='file',
|
||||
help=(
|
||||
"Uninstall all the packages listed in the given requirements "
|
||||
"file. This option can be used multiple times."
|
||||
'Uninstall all the packages listed in the given requirements '
|
||||
'file. This option can be used multiple times.'
|
||||
),
|
||||
)
|
||||
self.cmd_opts.add_option(
|
||||
"-y",
|
||||
"--yes",
|
||||
dest="yes",
|
||||
action="store_true",
|
||||
'-y',
|
||||
'--yes',
|
||||
dest='yes',
|
||||
action='store_true',
|
||||
help="Don't ask for confirmation of uninstall deletions.",
|
||||
)
|
||||
|
||||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
session = self.get_default_session(options)
|
||||
|
||||
reqs_to_uninstall = {}
|
||||
|
|
@ -69,28 +69,28 @@ class UninstallCommand(Command, SessionCommandMixin):
|
|||
reqs_to_uninstall[canonicalize_name(req.name)] = req
|
||||
else:
|
||||
logger.warning(
|
||||
"Invalid requirement: %r ignored -"
|
||||
" the uninstall command expects named"
|
||||
" requirements.",
|
||||
'Invalid requirement: %r ignored -'
|
||||
' the uninstall command expects named'
|
||||
' requirements.',
|
||||
name,
|
||||
)
|
||||
for filename in options.requirements:
|
||||
for parsed_req in parse_requirements(
|
||||
filename, options=options, session=session
|
||||
filename, options=options, session=session,
|
||||
):
|
||||
req = install_req_from_parsed_requirement(
|
||||
parsed_req, isolated=options.isolated_mode
|
||||
parsed_req, isolated=options.isolated_mode,
|
||||
)
|
||||
if req.name:
|
||||
reqs_to_uninstall[canonicalize_name(req.name)] = req
|
||||
if not reqs_to_uninstall:
|
||||
raise InstallationError(
|
||||
f"You must give at least one requirement to {self.name} (see "
|
||||
f'"pip help {self.name}")'
|
||||
f'You must give at least one requirement to {self.name} (see '
|
||||
f'"pip help {self.name}")',
|
||||
)
|
||||
|
||||
protect_pip_from_modification_on_windows(
|
||||
modifying_pip="pip" in reqs_to_uninstall
|
||||
modifying_pip='pip' in reqs_to_uninstall,
|
||||
)
|
||||
|
||||
for req in reqs_to_uninstall.values():
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
|
|
@ -6,14 +8,17 @@ from typing import List
|
|||
|
||||
from pip._internal.cache import WheelCache
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.req_command import RequirementCommand, with_cleanup
|
||||
from pip._internal.cli.req_command import RequirementCommand
|
||||
from pip._internal.cli.req_command import with_cleanup
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.req.req_tracker import get_requirement_tracker
|
||||
from pip._internal.utils.misc import ensure_dir, normalize_path
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
from pip._internal.utils.misc import normalize_path
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._internal.wheel_builder import build, should_build_for_wheel_command
|
||||
from pip._internal.wheel_builder import build
|
||||
from pip._internal.wheel_builder import should_build_for_wheel_command
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -43,14 +48,14 @@ class WheelCommand(RequirementCommand):
|
|||
def add_options(self) -> None:
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"-w",
|
||||
"--wheel-dir",
|
||||
dest="wheel_dir",
|
||||
metavar="dir",
|
||||
'-w',
|
||||
'--wheel-dir',
|
||||
dest='wheel_dir',
|
||||
metavar='dir',
|
||||
default=os.curdir,
|
||||
help=(
|
||||
"Build wheels into <dir>, where the default is the "
|
||||
"current working directory."
|
||||
'Build wheels into <dir>, where the default is the '
|
||||
'current working directory.'
|
||||
),
|
||||
)
|
||||
self.cmd_opts.add_option(cmdoptions.no_binary())
|
||||
|
|
@ -68,9 +73,9 @@ class WheelCommand(RequirementCommand):
|
|||
self.cmd_opts.add_option(cmdoptions.progress_bar())
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--no-verify",
|
||||
dest="no_verify",
|
||||
action="store_true",
|
||||
'--no-verify',
|
||||
dest='no_verify',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Don't verify if built wheel is valid.",
|
||||
)
|
||||
|
|
@ -79,12 +84,12 @@ class WheelCommand(RequirementCommand):
|
|||
self.cmd_opts.add_option(cmdoptions.global_options())
|
||||
|
||||
self.cmd_opts.add_option(
|
||||
"--pre",
|
||||
action="store_true",
|
||||
'--pre',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=(
|
||||
"Include pre-release and development versions. By default, "
|
||||
"pip only finds stable versions."
|
||||
'Include pre-release and development versions. By default, '
|
||||
'pip only finds stable versions.'
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -99,7 +104,7 @@ class WheelCommand(RequirementCommand):
|
|||
self.parser.insert_option_group(0, self.cmd_opts)
|
||||
|
||||
@with_cleanup
|
||||
def run(self, options: Values, args: List[str]) -> int:
|
||||
def run(self, options: Values, args: list[str]) -> int:
|
||||
cmdoptions.check_install_build_global(options)
|
||||
|
||||
session = self.get_default_session(options)
|
||||
|
|
@ -114,7 +119,7 @@ class WheelCommand(RequirementCommand):
|
|||
|
||||
directory = TempDirectory(
|
||||
delete=not options.no_clean,
|
||||
kind="wheel",
|
||||
kind='wheel',
|
||||
globally_managed=True,
|
||||
)
|
||||
|
||||
|
|
@ -144,7 +149,7 @@ class WheelCommand(RequirementCommand):
|
|||
|
||||
requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
|
||||
|
||||
reqs_to_build: List[InstallRequirement] = []
|
||||
reqs_to_build: list[InstallRequirement] = []
|
||||
for req in requirement_set.requirements.values():
|
||||
if req.is_wheel:
|
||||
preparer.save_linked_requirement(req)
|
||||
|
|
@ -167,12 +172,12 @@ class WheelCommand(RequirementCommand):
|
|||
shutil.copy(req.local_file_path, options.wheel_dir)
|
||||
except OSError as e:
|
||||
logger.warning(
|
||||
"Building wheel for %s failed: %s",
|
||||
'Building wheel for %s failed: %s',
|
||||
req.name,
|
||||
e,
|
||||
)
|
||||
build_failures.append(req)
|
||||
if len(build_failures) != 0:
|
||||
raise CommandError("Failed to build one or more wheels")
|
||||
raise CommandError('Failed to build one or more wheels')
|
||||
|
||||
return SUCCESS
|
||||
|
|
|
|||
|
|
@ -10,35 +10,41 @@ Some terminology:
|
|||
- variant
|
||||
A single word describing where the configuration key-value pair came from
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import locale
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import NewType
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.exceptions import (
|
||||
ConfigurationError,
|
||||
ConfigurationFileCouldNotBeLoaded,
|
||||
)
|
||||
from pip._internal.exceptions import ConfigurationError
|
||||
from pip._internal.exceptions import ConfigurationFileCouldNotBeLoaded
|
||||
from pip._internal.utils import appdirs
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.logging import getLogger
|
||||
from pip._internal.utils.misc import ensure_dir, enum
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
from pip._internal.utils.misc import enum
|
||||
|
||||
RawConfigParser = configparser.RawConfigParser # Shorthand
|
||||
Kind = NewType("Kind", str)
|
||||
Kind = NewType('Kind', str)
|
||||
|
||||
CONFIG_BASENAME = "pip.ini" if WINDOWS else "pip.conf"
|
||||
ENV_NAMES_IGNORED = "version", "help"
|
||||
CONFIG_BASENAME = 'pip.ini' if WINDOWS else 'pip.conf'
|
||||
ENV_NAMES_IGNORED = 'version', 'help'
|
||||
|
||||
# The kinds of configurations there are.
|
||||
kinds = enum(
|
||||
USER="user", # User Specific
|
||||
GLOBAL="global", # System Wide
|
||||
SITE="site", # [Virtual] Environment Specific
|
||||
ENV="env", # from PIP_CONFIG_FILE
|
||||
ENV_VAR="env-var", # from Environment Variables
|
||||
USER='user', # User Specific
|
||||
GLOBAL='global', # System Wide
|
||||
SITE='site', # [Virtual] Environment Specific
|
||||
ENV='env', # from PIP_CONFIG_FILE
|
||||
ENV_VAR='env-var', # from Environment Variables
|
||||
)
|
||||
OVERRIDE_ORDER = kinds.GLOBAL, kinds.USER, kinds.SITE, kinds.ENV, kinds.ENV_VAR
|
||||
VALID_LOAD_ONLY = kinds.USER, kinds.GLOBAL, kinds.SITE
|
||||
|
|
@ -49,34 +55,34 @@ logger = getLogger(__name__)
|
|||
# NOTE: Maybe use the optionx attribute to normalize keynames.
|
||||
def _normalize_name(name: str) -> str:
|
||||
"""Make a name consistent regardless of source (environment or file)"""
|
||||
name = name.lower().replace("_", "-")
|
||||
if name.startswith("--"):
|
||||
name = name.lower().replace('_', '-')
|
||||
if name.startswith('--'):
|
||||
name = name[2:] # only prefer long opts
|
||||
return name
|
||||
|
||||
|
||||
def _disassemble_key(name: str) -> List[str]:
|
||||
if "." not in name:
|
||||
def _disassemble_key(name: str) -> list[str]:
|
||||
if '.' not in name:
|
||||
error_message = (
|
||||
"Key does not contain dot separated section and key. "
|
||||
'Key does not contain dot separated section and key. '
|
||||
"Perhaps you wanted to use 'global.{}' instead?"
|
||||
).format(name)
|
||||
raise ConfigurationError(error_message)
|
||||
return name.split(".", 1)
|
||||
return name.split('.', 1)
|
||||
|
||||
|
||||
def get_configuration_files() -> Dict[Kind, List[str]]:
|
||||
def get_configuration_files() -> dict[Kind, list[str]]:
|
||||
global_config_files = [
|
||||
os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs("pip")
|
||||
os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs('pip')
|
||||
]
|
||||
|
||||
site_config_file = os.path.join(sys.prefix, CONFIG_BASENAME)
|
||||
legacy_config_file = os.path.join(
|
||||
os.path.expanduser("~"),
|
||||
"pip" if WINDOWS else ".pip",
|
||||
os.path.expanduser('~'),
|
||||
'pip' if WINDOWS else '.pip',
|
||||
CONFIG_BASENAME,
|
||||
)
|
||||
new_config_file = os.path.join(appdirs.user_config_dir("pip"), CONFIG_BASENAME)
|
||||
new_config_file = os.path.join(appdirs.user_config_dir('pip'), CONFIG_BASENAME)
|
||||
return {
|
||||
kinds.GLOBAL: global_config_files,
|
||||
kinds.SITE: [site_config_file],
|
||||
|
|
@ -98,26 +104,26 @@ class Configuration:
|
|||
and the data stored is also nice.
|
||||
"""
|
||||
|
||||
def __init__(self, isolated: bool, load_only: Optional[Kind] = None) -> None:
|
||||
def __init__(self, isolated: bool, load_only: Kind | None = None) -> None:
|
||||
super().__init__()
|
||||
|
||||
if load_only is not None and load_only not in VALID_LOAD_ONLY:
|
||||
raise ConfigurationError(
|
||||
"Got invalid value for load_only - should be one of {}".format(
|
||||
", ".join(map(repr, VALID_LOAD_ONLY))
|
||||
)
|
||||
'Got invalid value for load_only - should be one of {}'.format(
|
||||
', '.join(map(repr, VALID_LOAD_ONLY)),
|
||||
),
|
||||
)
|
||||
self.isolated = isolated
|
||||
self.load_only = load_only
|
||||
|
||||
# Because we keep track of where we got the data from
|
||||
self._parsers: Dict[Kind, List[Tuple[str, RawConfigParser]]] = {
|
||||
self._parsers: dict[Kind, list[tuple[str, RawConfigParser]]] = {
|
||||
variant: [] for variant in OVERRIDE_ORDER
|
||||
}
|
||||
self._config: Dict[Kind, Dict[str, Any]] = {
|
||||
self._config: dict[Kind, dict[str, Any]] = {
|
||||
variant: {} for variant in OVERRIDE_ORDER
|
||||
}
|
||||
self._modified_parsers: List[Tuple[str, RawConfigParser]] = []
|
||||
self._modified_parsers: list[tuple[str, RawConfigParser]] = []
|
||||
|
||||
def load(self) -> None:
|
||||
"""Loads configuration from configuration files and environment"""
|
||||
|
|
@ -125,16 +131,16 @@ class Configuration:
|
|||
if not self.isolated:
|
||||
self._load_environment_vars()
|
||||
|
||||
def get_file_to_edit(self) -> Optional[str]:
|
||||
def get_file_to_edit(self) -> str | None:
|
||||
"""Returns the file with highest priority in configuration"""
|
||||
assert self.load_only is not None, "Need to be specified a file to be editing"
|
||||
assert self.load_only is not None, 'Need to be specified a file to be editing'
|
||||
|
||||
try:
|
||||
return self._get_parser_to_modify()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def items(self) -> Iterable[Tuple[str, Any]]:
|
||||
def items(self) -> Iterable[tuple[str, Any]]:
|
||||
"""Returns key-value pairs like dict.items() representing the loaded
|
||||
configuration
|
||||
"""
|
||||
|
|
@ -145,7 +151,7 @@ class Configuration:
|
|||
try:
|
||||
return self._dictionary[key]
|
||||
except KeyError:
|
||||
raise ConfigurationError(f"No such key - {key}")
|
||||
raise ConfigurationError(f'No such key - {key}')
|
||||
|
||||
def set_value(self, key: str, value: Any) -> None:
|
||||
"""Modify a value in the configuration."""
|
||||
|
|
@ -171,7 +177,7 @@ class Configuration:
|
|||
|
||||
assert self.load_only
|
||||
if key not in self._config[self.load_only]:
|
||||
raise ConfigurationError(f"No such key - {key}")
|
||||
raise ConfigurationError(f'No such key - {key}')
|
||||
|
||||
fname, parser = self._get_parser_to_modify()
|
||||
|
||||
|
|
@ -182,7 +188,7 @@ class Configuration:
|
|||
):
|
||||
# The option was not removed.
|
||||
raise ConfigurationError(
|
||||
"Fatal Internal error [id=1]. Please report as a bug."
|
||||
'Fatal Internal error [id=1]. Please report as a bug.',
|
||||
)
|
||||
|
||||
# The section may be empty after the option was removed.
|
||||
|
|
@ -197,12 +203,12 @@ class Configuration:
|
|||
self._ensure_have_load_only()
|
||||
|
||||
for fname, parser in self._modified_parsers:
|
||||
logger.info("Writing to %s", fname)
|
||||
logger.info('Writing to %s', fname)
|
||||
|
||||
# Ensure directory exists.
|
||||
ensure_dir(os.path.dirname(fname))
|
||||
|
||||
with open(fname, "w") as f:
|
||||
with open(fname, 'w') as f:
|
||||
parser.write(f)
|
||||
|
||||
#
|
||||
|
|
@ -211,11 +217,11 @@ class Configuration:
|
|||
|
||||
def _ensure_have_load_only(self) -> None:
|
||||
if self.load_only is None:
|
||||
raise ConfigurationError("Needed a specific file to be modifying.")
|
||||
logger.debug("Will be working with %s variant only", self.load_only)
|
||||
raise ConfigurationError('Needed a specific file to be modifying.')
|
||||
logger.debug('Will be working with %s variant only', self.load_only)
|
||||
|
||||
@property
|
||||
def _dictionary(self) -> Dict[str, Any]:
|
||||
def _dictionary(self) -> dict[str, Any]:
|
||||
"""A dictionary representing the loaded configuration."""
|
||||
# NOTE: Dictionaries are not populated if not loaded. So, conditionals
|
||||
# are not needed here.
|
||||
|
|
@ -231,8 +237,8 @@ class Configuration:
|
|||
config_files = dict(self.iter_config_files())
|
||||
if config_files[kinds.ENV][0:1] == [os.devnull]:
|
||||
logger.debug(
|
||||
"Skipping loading configuration files due to "
|
||||
"environment's PIP_CONFIG_FILE being os.devnull"
|
||||
'Skipping loading configuration files due to '
|
||||
"environment's PIP_CONFIG_FILE being os.devnull",
|
||||
)
|
||||
return
|
||||
|
||||
|
|
@ -272,7 +278,7 @@ class Configuration:
|
|||
except UnicodeDecodeError:
|
||||
# See https://github.com/pypa/pip/issues/4963
|
||||
raise ConfigurationFileCouldNotBeLoaded(
|
||||
reason=f"contains invalid {locale_encoding} characters",
|
||||
reason=f'contains invalid {locale_encoding} characters',
|
||||
fname=fname,
|
||||
)
|
||||
except configparser.Error as error:
|
||||
|
|
@ -283,12 +289,12 @@ class Configuration:
|
|||
def _load_environment_vars(self) -> None:
|
||||
"""Loads configuration from environment variables"""
|
||||
self._config[kinds.ENV_VAR].update(
|
||||
self._normalized_keys(":env:", self.get_environ_vars())
|
||||
self._normalized_keys(':env:', self.get_environ_vars()),
|
||||
)
|
||||
|
||||
def _normalized_keys(
|
||||
self, section: str, items: Iterable[Tuple[str, Any]]
|
||||
) -> Dict[str, Any]:
|
||||
self, section: str, items: Iterable[tuple[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
"""Normalizes items to construct a dictionary with normalized keys.
|
||||
|
||||
This routine is where the names become keys and are made the same
|
||||
|
|
@ -296,20 +302,20 @@ class Configuration:
|
|||
"""
|
||||
normalized = {}
|
||||
for name, val in items:
|
||||
key = section + "." + _normalize_name(name)
|
||||
key = section + '.' + _normalize_name(name)
|
||||
normalized[key] = val
|
||||
return normalized
|
||||
|
||||
def get_environ_vars(self) -> Iterable[Tuple[str, str]]:
|
||||
def get_environ_vars(self) -> Iterable[tuple[str, str]]:
|
||||
"""Returns a generator with all environmental vars with prefix PIP_"""
|
||||
for key, val in os.environ.items():
|
||||
if key.startswith("PIP_"):
|
||||
if key.startswith('PIP_'):
|
||||
name = key[4:].lower()
|
||||
if name not in ENV_NAMES_IGNORED:
|
||||
yield name, val
|
||||
|
||||
# XXX: This is patched in the tests.
|
||||
def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]:
|
||||
def iter_config_files(self) -> Iterable[tuple[Kind, list[str]]]:
|
||||
"""Yields variant and configuration files associated with it.
|
||||
|
||||
This should be treated like items of a dictionary.
|
||||
|
|
@ -317,7 +323,7 @@ class Configuration:
|
|||
# SMELL: Move the conditions out of this function
|
||||
|
||||
# environment variables have the lowest priority
|
||||
config_file = os.environ.get("PIP_CONFIG_FILE", None)
|
||||
config_file = os.environ.get('PIP_CONFIG_FILE', None)
|
||||
if config_file is not None:
|
||||
yield kinds.ENV, [config_file]
|
||||
else:
|
||||
|
|
@ -339,18 +345,18 @@ class Configuration:
|
|||
# finally virtualenv configuration first trumping others
|
||||
yield kinds.SITE, config_files[kinds.SITE]
|
||||
|
||||
def get_values_in_config(self, variant: Kind) -> Dict[str, Any]:
|
||||
def get_values_in_config(self, variant: Kind) -> dict[str, Any]:
|
||||
"""Get values present in a config file"""
|
||||
return self._config[variant]
|
||||
|
||||
def _get_parser_to_modify(self) -> Tuple[str, RawConfigParser]:
|
||||
def _get_parser_to_modify(self) -> tuple[str, RawConfigParser]:
|
||||
# Determine which parser to modify
|
||||
assert self.load_only
|
||||
parsers = self._parsers[self.load_only]
|
||||
if not parsers:
|
||||
# This should not happen if everything works correctly.
|
||||
raise ConfigurationError(
|
||||
"Fatal Internal error [id=2]. Please report as a bug."
|
||||
'Fatal Internal error [id=2]. Please report as a bug.',
|
||||
)
|
||||
|
||||
# Use the highest priority parser.
|
||||
|
|
@ -363,4 +369,4 @@ class Configuration:
|
|||
self._modified_parsers.append(file_parser_tuple)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self._dictionary!r})"
|
||||
return f'{self.__class__.__name__}({self._dictionary!r})'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pip._internal.distributions.base import AbstractDistribution
|
||||
from pip._internal.distributions.sdist import SourceDistribution
|
||||
from pip._internal.distributions.wheel import WheelDistribution
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
|
|
@ -31,6 +33,6 @@ class AbstractDistribution(metaclass=abc.ABCMeta):
|
|||
|
||||
@abc.abstractmethod
|
||||
def prepare_distribution_metadata(
|
||||
self, finder: PackageFinder, build_isolation: bool
|
||||
self, finder: PackageFinder, build_isolation: bool,
|
||||
) -> None:
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pip._internal.distributions.base import AbstractDistribution
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
|
|
@ -11,10 +13,10 @@ class InstalledDistribution(AbstractDistribution):
|
|||
"""
|
||||
|
||||
def get_metadata_distribution(self) -> BaseDistribution:
|
||||
assert self.req.satisfied_by is not None, "not actually installed"
|
||||
assert self.req.satisfied_by is not None, 'not actually installed'
|
||||
return self.req.satisfied_by
|
||||
|
||||
def prepare_distribution_metadata(
|
||||
self, finder: PackageFinder, build_isolation: bool
|
||||
self, finder: PackageFinder, build_isolation: bool,
|
||||
) -> None:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Iterable, Set, Tuple
|
||||
from typing import Iterable
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.distributions.base import AbstractDistribution
|
||||
|
|
@ -22,7 +26,7 @@ class SourceDistribution(AbstractDistribution):
|
|||
return self.req.get_dist()
|
||||
|
||||
def prepare_distribution_metadata(
|
||||
self, finder: PackageFinder, build_isolation: bool
|
||||
self, finder: PackageFinder, build_isolation: bool,
|
||||
) -> None:
|
||||
# Load pyproject.toml, to determine whether PEP 517 is to be used
|
||||
self.req.load_pyproject_toml()
|
||||
|
|
@ -54,27 +58,27 @@ class SourceDistribution(AbstractDistribution):
|
|||
|
||||
self.req.build_env = BuildEnvironment()
|
||||
self.req.build_env.install_requirements(
|
||||
finder, pyproject_requires, "overlay", kind="build dependencies"
|
||||
finder, pyproject_requires, 'overlay', kind='build dependencies',
|
||||
)
|
||||
conflicting, missing = self.req.build_env.check_requirements(
|
||||
self.req.requirements_to_check
|
||||
self.req.requirements_to_check,
|
||||
)
|
||||
if conflicting:
|
||||
self._raise_conflicts("PEP 517/518 supported requirements", conflicting)
|
||||
self._raise_conflicts('PEP 517/518 supported requirements', conflicting)
|
||||
if missing:
|
||||
logger.warning(
|
||||
"Missing build requirements in pyproject.toml for %s.",
|
||||
'Missing build requirements in pyproject.toml for %s.',
|
||||
self.req,
|
||||
)
|
||||
logger.warning(
|
||||
"The project does not specify a build backend, and "
|
||||
"pip cannot fall back to setuptools without %s.",
|
||||
" and ".join(map(repr, sorted(missing))),
|
||||
'The project does not specify a build backend, and '
|
||||
'pip cannot fall back to setuptools without %s.',
|
||||
' and '.join(map(repr, sorted(missing))),
|
||||
)
|
||||
|
||||
def _get_build_requires_wheel(self) -> Iterable[str]:
|
||||
with self.req.build_env:
|
||||
runner = runner_with_spinner_message("Getting requirements to build wheel")
|
||||
runner = runner_with_spinner_message('Getting requirements to build wheel')
|
||||
backend = self.req.pep517_backend
|
||||
assert backend is not None
|
||||
with backend.subprocess_runner(runner):
|
||||
|
|
@ -83,7 +87,7 @@ class SourceDistribution(AbstractDistribution):
|
|||
def _get_build_requires_editable(self) -> Iterable[str]:
|
||||
with self.req.build_env:
|
||||
runner = runner_with_spinner_message(
|
||||
"Getting requirements to build editable"
|
||||
'Getting requirements to build editable',
|
||||
)
|
||||
backend = self.req.pep517_backend
|
||||
assert backend is not None
|
||||
|
|
@ -95,32 +99,32 @@ class SourceDistribution(AbstractDistribution):
|
|||
# This must be done in a second pass, as the pyproject.toml
|
||||
# dependencies must be installed before we can call the backend.
|
||||
if (
|
||||
self.req.editable
|
||||
and self.req.permit_editable_wheels
|
||||
and self.req.supports_pyproject_editable()
|
||||
self.req.editable and
|
||||
self.req.permit_editable_wheels and
|
||||
self.req.supports_pyproject_editable()
|
||||
):
|
||||
build_reqs = self._get_build_requires_editable()
|
||||
else:
|
||||
build_reqs = self._get_build_requires_wheel()
|
||||
conflicting, missing = self.req.build_env.check_requirements(build_reqs)
|
||||
if conflicting:
|
||||
self._raise_conflicts("the backend dependencies", conflicting)
|
||||
self._raise_conflicts('the backend dependencies', conflicting)
|
||||
self.req.build_env.install_requirements(
|
||||
finder, missing, "normal", kind="backend dependencies"
|
||||
finder, missing, 'normal', kind='backend dependencies',
|
||||
)
|
||||
|
||||
def _raise_conflicts(
|
||||
self, conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]]
|
||||
self, conflicting_with: str, conflicting_reqs: set[tuple[str, str]],
|
||||
) -> None:
|
||||
format_string = (
|
||||
"Some build dependencies for {requirement} "
|
||||
"conflict with {conflicting_with}: {description}."
|
||||
'Some build dependencies for {requirement} '
|
||||
'conflict with {conflicting_with}: {description}.'
|
||||
)
|
||||
error_message = format_string.format(
|
||||
requirement=self.req,
|
||||
conflicting_with=conflicting_with,
|
||||
description=", ".join(
|
||||
f"{installed} is incompatible with {wanted}"
|
||||
description=', '.join(
|
||||
f'{installed} is incompatible with {wanted}'
|
||||
for installed, wanted in sorted(conflicting_reqs)
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from __future__ import annotations
|
||||
|
||||
from pip._internal.distributions.base import AbstractDistribution
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import (
|
||||
BaseDistribution,
|
||||
FilesystemWheel,
|
||||
get_wheel_distribution,
|
||||
)
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.metadata import FilesystemWheel
|
||||
from pip._internal.metadata import get_wheel_distribution
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
|
||||
class WheelDistribution(AbstractDistribution):
|
||||
|
|
@ -20,12 +19,12 @@ class WheelDistribution(AbstractDistribution):
|
|||
Distribution that uses it, not relying on the wheel file or
|
||||
requirement.
|
||||
"""
|
||||
assert self.req.local_file_path, "Set as part of preparation during download"
|
||||
assert self.req.name, "Wheels are never unnamed"
|
||||
assert self.req.local_file_path, 'Set as part of preparation during download'
|
||||
assert self.req.name, 'Wheels are never unnamed'
|
||||
wheel = FilesystemWheel(self.req.local_file_path)
|
||||
return get_wheel_distribution(wheel, canonicalize_name(self.req.name))
|
||||
|
||||
def prepare_distribution_metadata(
|
||||
self, finder: PackageFinder, build_isolation: bool
|
||||
self, finder: PackageFinder, build_isolation: bool,
|
||||
) -> None:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -4,14 +4,24 @@ This module MUST NOT try to import from anything within `pip._internal` to
|
|||
operate. This is expected to be importable from any/all files within the
|
||||
subpackage and, thus, should not depend on them.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
import re
|
||||
from itertools import chain, groupby, repeat
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
||||
from itertools import chain
|
||||
from itertools import groupby
|
||||
from itertools import repeat
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from pip._vendor.requests.models import Request, Response
|
||||
from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult
|
||||
from pip._vendor.requests.models import Request
|
||||
from pip._vendor.requests.models import Response
|
||||
from pip._vendor.rich.console import Console
|
||||
from pip._vendor.rich.console import ConsoleOptions
|
||||
from pip._vendor.rich.console import RenderResult
|
||||
from pip._vendor.rich.markup import escape
|
||||
from pip._vendor.rich.text import Text
|
||||
|
||||
|
|
@ -27,11 +37,11 @@ if TYPE_CHECKING:
|
|||
# Scaffolding
|
||||
#
|
||||
def _is_kebab_case(s: str) -> bool:
|
||||
return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None
|
||||
return re.match(r'^[a-z]+(-[a-z]+)*$', s) is not None
|
||||
|
||||
|
||||
def _prefix_with_indent(
|
||||
s: Union[Text, str],
|
||||
s: Text | str,
|
||||
console: Console,
|
||||
*,
|
||||
prefix: str,
|
||||
|
|
@ -42,8 +52,8 @@ def _prefix_with_indent(
|
|||
else:
|
||||
text = console.render_str(s)
|
||||
|
||||
return console.render_str(prefix, overflow="ignore") + console.render_str(
|
||||
f"\n{indent}", overflow="ignore"
|
||||
return console.render_str(prefix, overflow='ignore') + console.render_str(
|
||||
f'\n{indent}', overflow='ignore',
|
||||
).join(text.split(allow_blank=True))
|
||||
|
||||
|
||||
|
|
@ -67,19 +77,19 @@ class DiagnosticPipError(PipError):
|
|||
def __init__(
|
||||
self,
|
||||
*,
|
||||
kind: 'Literal["error", "warning"]' = "error",
|
||||
reference: Optional[str] = None,
|
||||
message: Union[str, Text],
|
||||
context: Optional[Union[str, Text]],
|
||||
hint_stmt: Optional[Union[str, Text]],
|
||||
note_stmt: Optional[Union[str, Text]] = None,
|
||||
link: Optional[str] = None,
|
||||
kind: Literal["error", "warning"] = 'error',
|
||||
reference: str | None = None,
|
||||
message: str | Text,
|
||||
context: str | Text | None,
|
||||
hint_stmt: str | Text | None,
|
||||
note_stmt: str | Text | None = None,
|
||||
link: str | None = None,
|
||||
) -> None:
|
||||
# Ensure a proper reference is provided.
|
||||
if reference is None:
|
||||
assert hasattr(self, "reference"), "error reference not provided!"
|
||||
assert hasattr(self, 'reference'), 'error reference not provided!'
|
||||
reference = self.reference
|
||||
assert _is_kebab_case(reference), "error reference must be kebab-case!"
|
||||
assert _is_kebab_case(reference), 'error reference must be kebab-case!'
|
||||
|
||||
self.kind = kind
|
||||
self.reference = reference
|
||||
|
|
@ -92,17 +102,17 @@ class DiagnosticPipError(PipError):
|
|||
|
||||
self.link = link
|
||||
|
||||
super().__init__(f"<{self.__class__.__name__}: {self.reference}>")
|
||||
super().__init__(f'<{self.__class__.__name__}: {self.reference}>')
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<{self.__class__.__name__}("
|
||||
f"reference={self.reference!r}, "
|
||||
f"message={self.message!r}, "
|
||||
f"context={self.context!r}, "
|
||||
f"note_stmt={self.note_stmt!r}, "
|
||||
f"hint_stmt={self.hint_stmt!r}"
|
||||
")>"
|
||||
f'<{self.__class__.__name__}('
|
||||
f'reference={self.reference!r}, '
|
||||
f'message={self.message!r}, '
|
||||
f'context={self.context!r}, '
|
||||
f'note_stmt={self.note_stmt!r}, '
|
||||
f'hint_stmt={self.hint_stmt!r}'
|
||||
')>'
|
||||
)
|
||||
|
||||
def __rich_console__(
|
||||
|
|
@ -110,10 +120,10 @@ class DiagnosticPipError(PipError):
|
|||
console: Console,
|
||||
options: ConsoleOptions,
|
||||
) -> RenderResult:
|
||||
colour = "red" if self.kind == "error" else "yellow"
|
||||
colour = 'red' if self.kind == 'error' else 'yellow'
|
||||
|
||||
yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]"
|
||||
yield ""
|
||||
yield f'[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]'
|
||||
yield ''
|
||||
|
||||
if not options.ascii_only:
|
||||
# Present the main message, with relevant context indented.
|
||||
|
|
@ -121,49 +131,49 @@ class DiagnosticPipError(PipError):
|
|||
yield _prefix_with_indent(
|
||||
self.message,
|
||||
console,
|
||||
prefix=f"[{colour}]×[/] ",
|
||||
indent=f"[{colour}]│[/] ",
|
||||
prefix=f'[{colour}]×[/] ',
|
||||
indent=f'[{colour}]│[/] ',
|
||||
)
|
||||
yield _prefix_with_indent(
|
||||
self.context,
|
||||
console,
|
||||
prefix=f"[{colour}]╰─>[/] ",
|
||||
indent=f"[{colour}] [/] ",
|
||||
prefix=f'[{colour}]╰─>[/] ',
|
||||
indent=f'[{colour}] [/] ',
|
||||
)
|
||||
else:
|
||||
yield _prefix_with_indent(
|
||||
self.message,
|
||||
console,
|
||||
prefix="[red]×[/] ",
|
||||
indent=" ",
|
||||
prefix='[red]×[/] ',
|
||||
indent=' ',
|
||||
)
|
||||
else:
|
||||
yield self.message
|
||||
if self.context is not None:
|
||||
yield ""
|
||||
yield ''
|
||||
yield self.context
|
||||
|
||||
if self.note_stmt is not None or self.hint_stmt is not None:
|
||||
yield ""
|
||||
yield ''
|
||||
|
||||
if self.note_stmt is not None:
|
||||
yield _prefix_with_indent(
|
||||
self.note_stmt,
|
||||
console,
|
||||
prefix="[magenta bold]note[/]: ",
|
||||
indent=" ",
|
||||
prefix='[magenta bold]note[/]: ',
|
||||
indent=' ',
|
||||
)
|
||||
if self.hint_stmt is not None:
|
||||
yield _prefix_with_indent(
|
||||
self.hint_stmt,
|
||||
console,
|
||||
prefix="[cyan bold]hint[/]: ",
|
||||
indent=" ",
|
||||
prefix='[cyan bold]hint[/]: ',
|
||||
indent=' ',
|
||||
)
|
||||
|
||||
if self.link is not None:
|
||||
yield ""
|
||||
yield f"Link: {self.link}"
|
||||
yield ''
|
||||
yield f'Link: {self.link}'
|
||||
|
||||
|
||||
#
|
||||
|
|
@ -184,34 +194,34 @@ class UninstallationError(PipError):
|
|||
class MissingPyProjectBuildRequires(DiagnosticPipError):
|
||||
"""Raised when pyproject.toml has `build-system`, but no `build-system.requires`."""
|
||||
|
||||
reference = "missing-pyproject-build-system-requires"
|
||||
reference = 'missing-pyproject-build-system-requires'
|
||||
|
||||
def __init__(self, *, package: str) -> None:
|
||||
super().__init__(
|
||||
message=f"Can not process {escape(package)}",
|
||||
message=f'Can not process {escape(package)}',
|
||||
context=Text(
|
||||
"This package has an invalid pyproject.toml file.\n"
|
||||
"The [build-system] table is missing the mandatory `requires` key."
|
||||
'This package has an invalid pyproject.toml file.\n'
|
||||
'The [build-system] table is missing the mandatory `requires` key.',
|
||||
),
|
||||
note_stmt="This is an issue with the package mentioned above, not pip.",
|
||||
hint_stmt=Text("See PEP 518 for the detailed specification."),
|
||||
note_stmt='This is an issue with the package mentioned above, not pip.',
|
||||
hint_stmt=Text('See PEP 518 for the detailed specification.'),
|
||||
)
|
||||
|
||||
|
||||
class InvalidPyProjectBuildRequires(DiagnosticPipError):
|
||||
"""Raised when pyproject.toml an invalid `build-system.requires`."""
|
||||
|
||||
reference = "invalid-pyproject-build-system-requires"
|
||||
reference = 'invalid-pyproject-build-system-requires'
|
||||
|
||||
def __init__(self, *, package: str, reason: str) -> None:
|
||||
super().__init__(
|
||||
message=f"Can not process {escape(package)}",
|
||||
message=f'Can not process {escape(package)}',
|
||||
context=Text(
|
||||
"This package has an invalid `build-system.requires` key in "
|
||||
f"pyproject.toml.\n{reason}"
|
||||
'This package has an invalid `build-system.requires` key in '
|
||||
f'pyproject.toml.\n{reason}',
|
||||
),
|
||||
note_stmt="This is an issue with the package mentioned above, not pip.",
|
||||
hint_stmt=Text("See PEP 518 for the detailed specification."),
|
||||
note_stmt='This is an issue with the package mentioned above, not pip.',
|
||||
hint_stmt=Text('See PEP 518 for the detailed specification.'),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -226,7 +236,7 @@ class NoneMetadataError(PipError):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
dist: "BaseDistribution",
|
||||
dist: BaseDistribution,
|
||||
metadata_name: str,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -240,7 +250,7 @@ class NoneMetadataError(PipError):
|
|||
def __str__(self) -> str:
|
||||
# Use `dist` in the error message because its stringification
|
||||
# includes more information, like the version and location.
|
||||
return "None {} metadata found for distribution: {}".format(
|
||||
return 'None {} metadata found for distribution: {}'.format(
|
||||
self.metadata_name,
|
||||
self.dist,
|
||||
)
|
||||
|
|
@ -250,13 +260,13 @@ class UserInstallationInvalid(InstallationError):
|
|||
"""A --user install is requested on an environment without user site."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "User base directory is not specified"
|
||||
return 'User base directory is not specified'
|
||||
|
||||
|
||||
class InvalidSchemeCombination(InstallationError):
|
||||
def __str__(self) -> str:
|
||||
before = ", ".join(str(a) for a in self.args[:-1])
|
||||
return f"Cannot set {before} and {self.args[-1]} together"
|
||||
before = ', '.join(str(a) for a in self.args[:-1])
|
||||
return f'Cannot set {before} and {self.args[-1]} together'
|
||||
|
||||
|
||||
class DistributionNotFound(InstallationError):
|
||||
|
|
@ -288,7 +298,7 @@ class NetworkConnectionError(PipError):
|
|||
"""HTTP connection error"""
|
||||
|
||||
def __init__(
|
||||
self, error_msg: str, response: Response = None, request: Request = None
|
||||
self, error_msg: str, response: Response = None, request: Request = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize NetworkConnectionError with `request` and `response`
|
||||
|
|
@ -298,9 +308,9 @@ class NetworkConnectionError(PipError):
|
|||
self.request = request
|
||||
self.error_msg = error_msg
|
||||
if (
|
||||
self.response is not None
|
||||
and not self.request
|
||||
and hasattr(response, "request")
|
||||
self.response is not None and
|
||||
not self.request and
|
||||
hasattr(response, 'request')
|
||||
):
|
||||
self.request = self.response.request
|
||||
super().__init__(error_msg, response, request)
|
||||
|
|
@ -337,7 +347,7 @@ class MetadataInconsistent(InstallationError):
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self, ireq: "InstallRequirement", field: str, f_val: str, m_val: str
|
||||
self, ireq: InstallRequirement, field: str, f_val: str, m_val: str,
|
||||
) -> None:
|
||||
self.ireq = ireq
|
||||
self.field = field
|
||||
|
|
@ -346,8 +356,8 @@ class MetadataInconsistent(InstallationError):
|
|||
|
||||
def __str__(self) -> str:
|
||||
template = (
|
||||
"Requested {} has inconsistent {}: "
|
||||
"filename has {!r}, but metadata has {!r}"
|
||||
'Requested {} has inconsistent {}: '
|
||||
'filename has {!r}, but metadata has {!r}'
|
||||
)
|
||||
return template.format(self.ireq, self.field, self.f_val, self.m_val)
|
||||
|
||||
|
|
@ -355,48 +365,48 @@ class MetadataInconsistent(InstallationError):
|
|||
class LegacyInstallFailure(DiagnosticPipError):
|
||||
"""Error occurred while executing `setup.py install`"""
|
||||
|
||||
reference = "legacy-install-failure"
|
||||
reference = 'legacy-install-failure'
|
||||
|
||||
def __init__(self, package_details: str) -> None:
|
||||
super().__init__(
|
||||
message="Encountered error while trying to install package.",
|
||||
message='Encountered error while trying to install package.',
|
||||
context=package_details,
|
||||
hint_stmt="See above for output from the failure.",
|
||||
note_stmt="This is an issue with the package mentioned above, not pip.",
|
||||
hint_stmt='See above for output from the failure.',
|
||||
note_stmt='This is an issue with the package mentioned above, not pip.',
|
||||
)
|
||||
|
||||
|
||||
class InstallationSubprocessError(DiagnosticPipError, InstallationError):
|
||||
"""A subprocess call failed."""
|
||||
|
||||
reference = "subprocess-exited-with-error"
|
||||
reference = 'subprocess-exited-with-error'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
command_description: str,
|
||||
exit_code: int,
|
||||
output_lines: Optional[List[str]],
|
||||
output_lines: list[str] | None,
|
||||
) -> None:
|
||||
if output_lines is None:
|
||||
output_prompt = Text("See above for output.")
|
||||
output_prompt = Text('See above for output.')
|
||||
else:
|
||||
output_prompt = (
|
||||
Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n")
|
||||
+ Text("".join(output_lines))
|
||||
+ Text.from_markup(R"[red]\[end of output][/]")
|
||||
Text.from_markup(f'[red][{len(output_lines)} lines of output][/]\n') +
|
||||
Text(''.join(output_lines)) +
|
||||
Text.from_markup(R'[red]\[end of output][/]')
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
message=(
|
||||
f"[green]{escape(command_description)}[/] did not run successfully.\n"
|
||||
f"exit code: {exit_code}"
|
||||
f'[green]{escape(command_description)}[/] did not run successfully.\n'
|
||||
f'exit code: {exit_code}'
|
||||
),
|
||||
context=output_prompt,
|
||||
hint_stmt=None,
|
||||
note_stmt=(
|
||||
"This error originates from a subprocess, and is likely not a "
|
||||
"problem with pip."
|
||||
'This error originates from a subprocess, and is likely not a '
|
||||
'problem with pip.'
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -404,11 +414,11 @@ class InstallationSubprocessError(DiagnosticPipError, InstallationError):
|
|||
self.exit_code = exit_code
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.command_description} exited with {self.exit_code}"
|
||||
return f'{self.command_description} exited with {self.exit_code}'
|
||||
|
||||
|
||||
class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):
|
||||
reference = "metadata-generation-failed"
|
||||
reference = 'metadata-generation-failed'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -416,23 +426,23 @@ class MetadataGenerationFailed(InstallationSubprocessError, InstallationError):
|
|||
package_details: str,
|
||||
) -> None:
|
||||
super(InstallationSubprocessError, self).__init__(
|
||||
message="Encountered error while generating package metadata.",
|
||||
message='Encountered error while generating package metadata.',
|
||||
context=escape(package_details),
|
||||
hint_stmt="See above for details.",
|
||||
note_stmt="This is an issue with the package mentioned above, not pip.",
|
||||
hint_stmt='See above for details.',
|
||||
note_stmt='This is an issue with the package mentioned above, not pip.',
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "metadata generation failed"
|
||||
return 'metadata generation failed'
|
||||
|
||||
|
||||
class HashErrors(InstallationError):
|
||||
"""Multiple HashError instances rolled into one for reporting"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.errors: List["HashError"] = []
|
||||
self.errors: list[HashError] = []
|
||||
|
||||
def append(self, error: "HashError") -> None:
|
||||
def append(self, error: HashError) -> None:
|
||||
self.errors.append(error)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
|
@ -442,8 +452,8 @@ class HashErrors(InstallationError):
|
|||
lines.append(cls.head)
|
||||
lines.extend(e.body() for e in errors_of_cls)
|
||||
if lines:
|
||||
return "\n".join(lines)
|
||||
return ""
|
||||
return '\n'.join(lines)
|
||||
return ''
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self.errors)
|
||||
|
|
@ -466,8 +476,8 @@ class HashError(InstallationError):
|
|||
|
||||
"""
|
||||
|
||||
req: Optional["InstallRequirement"] = None
|
||||
head = ""
|
||||
req: InstallRequirement | None = None
|
||||
head = ''
|
||||
order: int = -1
|
||||
|
||||
def body(self) -> str:
|
||||
|
|
@ -480,10 +490,10 @@ class HashError(InstallationError):
|
|||
its link already populated by the resolver's _populate_link().
|
||||
|
||||
"""
|
||||
return f" {self._requirement_name()}"
|
||||
return f' {self._requirement_name()}'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.head}\n{self.body()}"
|
||||
return f'{self.head}\n{self.body()}'
|
||||
|
||||
def _requirement_name(self) -> str:
|
||||
"""Return a description of the requirement that triggered me.
|
||||
|
|
@ -492,7 +502,7 @@ class HashError(InstallationError):
|
|||
line numbers
|
||||
|
||||
"""
|
||||
return str(self.req) if self.req else "unknown package"
|
||||
return str(self.req) if self.req else 'unknown package'
|
||||
|
||||
|
||||
class VcsHashUnsupported(HashError):
|
||||
|
|
@ -502,7 +512,7 @@ class VcsHashUnsupported(HashError):
|
|||
order = 0
|
||||
head = (
|
||||
"Can't verify hashes for these requirements because we don't "
|
||||
"have a way to hash version control repositories:"
|
||||
'have a way to hash version control repositories:'
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -513,7 +523,7 @@ class DirectoryUrlHashUnsupported(HashError):
|
|||
order = 1
|
||||
head = (
|
||||
"Can't verify hashes for these file:// requirements because they "
|
||||
"point to directories:"
|
||||
'point to directories:'
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -522,13 +532,13 @@ class HashMissing(HashError):
|
|||
|
||||
order = 2
|
||||
head = (
|
||||
"Hashes are required in --require-hashes mode, but they are "
|
||||
"missing from some requirements. Here is a list of those "
|
||||
"requirements along with the hashes their downloaded archives "
|
||||
"actually had. Add lines like these to your requirements files to "
|
||||
"prevent tampering. (If you did not enable --require-hashes "
|
||||
"manually, note that it turns on automatically when any package "
|
||||
"has a hash.)"
|
||||
'Hashes are required in --require-hashes mode, but they are '
|
||||
'missing from some requirements. Here is a list of those '
|
||||
'requirements along with the hashes their downloaded archives '
|
||||
'actually had. Add lines like these to your requirements files to '
|
||||
'prevent tampering. (If you did not enable --require-hashes '
|
||||
'manually, note that it turns on automatically when any package '
|
||||
'has a hash.)'
|
||||
)
|
||||
|
||||
def __init__(self, gotten_hash: str) -> None:
|
||||
|
|
@ -552,10 +562,10 @@ class HashMissing(HashError):
|
|||
if self.req.original_link
|
||||
# In case someone feeds something downright stupid
|
||||
# to InstallRequirement's constructor.
|
||||
else getattr(self.req, "req", None)
|
||||
else getattr(self.req, 'req', None)
|
||||
)
|
||||
return " {} --hash={}:{}".format(
|
||||
package or "unknown package", FAVORITE_HASH, self.gotten_hash
|
||||
return ' {} --hash={}:{}'.format(
|
||||
package or 'unknown package', FAVORITE_HASH, self.gotten_hash,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -565,8 +575,8 @@ class HashUnpinned(HashError):
|
|||
|
||||
order = 3
|
||||
head = (
|
||||
"In --require-hashes mode, all requirements must have their "
|
||||
"versions pinned with ==. These do not:"
|
||||
'In --require-hashes mode, all requirements must have their '
|
||||
'versions pinned with ==. These do not:'
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -582,13 +592,13 @@ class HashMismatch(HashError):
|
|||
|
||||
order = 4
|
||||
head = (
|
||||
"THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS "
|
||||
"FILE. If you have updated the package versions, please update "
|
||||
"the hashes. Otherwise, examine the package contents carefully; "
|
||||
"someone may have tampered with them."
|
||||
'THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS '
|
||||
'FILE. If you have updated the package versions, please update '
|
||||
'the hashes. Otherwise, examine the package contents carefully; '
|
||||
'someone may have tampered with them.'
|
||||
)
|
||||
|
||||
def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None:
|
||||
def __init__(self, allowed: dict[str, list[str]], gots: dict[str, _Hash]) -> None:
|
||||
"""
|
||||
:param allowed: A dict of algorithm names pointing to lists of allowed
|
||||
hex digests
|
||||
|
|
@ -599,7 +609,7 @@ class HashMismatch(HashError):
|
|||
self.gots = gots
|
||||
|
||||
def body(self) -> str:
|
||||
return " {}:\n{}".format(self._requirement_name(), self._hash_comparison())
|
||||
return f' {self._requirement_name()}:\n{self._hash_comparison()}'
|
||||
|
||||
def _hash_comparison(self) -> str:
|
||||
"""
|
||||
|
|
@ -613,21 +623,21 @@ class HashMismatch(HashError):
|
|||
|
||||
"""
|
||||
|
||||
def hash_then_or(hash_name: str) -> "chain[str]":
|
||||
def hash_then_or(hash_name: str) -> chain[str]:
|
||||
# For now, all the decent hashes have 6-char names, so we can get
|
||||
# away with hard-coding space literals.
|
||||
return chain([hash_name], repeat(" or"))
|
||||
return chain([hash_name], repeat(' or'))
|
||||
|
||||
lines: List[str] = []
|
||||
lines: list[str] = []
|
||||
for hash_name, expecteds in self.allowed.items():
|
||||
prefix = hash_then_or(hash_name)
|
||||
lines.extend(
|
||||
(" Expected {} {}".format(next(prefix), e)) for e in expecteds
|
||||
(f' Expected {next(prefix)} {e}') for e in expecteds
|
||||
)
|
||||
lines.append(
|
||||
" Got {}\n".format(self.gots[hash_name].hexdigest())
|
||||
f' Got {self.gots[hash_name].hexdigest()}\n',
|
||||
)
|
||||
return "\n".join(lines)
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
class UnsupportedPythonVersion(InstallationError):
|
||||
|
|
@ -640,9 +650,9 @@ class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
reason: str = "could not be loaded",
|
||||
fname: Optional[str] = None,
|
||||
error: Optional[configparser.Error] = None,
|
||||
reason: str = 'could not be loaded',
|
||||
fname: str | None = None,
|
||||
error: configparser.Error | None = None,
|
||||
) -> None:
|
||||
super().__init__(error)
|
||||
self.reason = reason
|
||||
|
|
@ -651,8 +661,8 @@ class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
|
|||
|
||||
def __str__(self) -> str:
|
||||
if self.fname is not None:
|
||||
message_part = f" in {self.fname}."
|
||||
message_part = f' in {self.fname}.'
|
||||
else:
|
||||
assert self.error is not None
|
||||
message_part = f".\n{self.error}\n"
|
||||
return f"Configuration file {self.reason}{message_part}"
|
||||
message_part = f'.\n{self.error}\n'
|
||||
return f'Configuration file {self.reason}{message_part}'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,45 +1,52 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import sysconfig
|
||||
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
|
||||
from pip._internal.models.scheme import Scheme
|
||||
from pip._internal.models.scheme import SCHEME_KEYS
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.deprecation import deprecated
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
|
||||
from . import _distutils, _sysconfig
|
||||
from .base import (
|
||||
USER_CACHE_DIR,
|
||||
get_major_minor_version,
|
||||
get_src_prefix,
|
||||
is_osx_framework,
|
||||
site_packages,
|
||||
user_site,
|
||||
)
|
||||
from . import _distutils
|
||||
from . import _sysconfig
|
||||
from .base import get_major_minor_version
|
||||
from .base import get_src_prefix
|
||||
from .base import is_osx_framework
|
||||
from .base import site_packages
|
||||
from .base import USER_CACHE_DIR
|
||||
from .base import user_site
|
||||
|
||||
__all__ = [
|
||||
"USER_CACHE_DIR",
|
||||
"get_bin_prefix",
|
||||
"get_bin_user",
|
||||
"get_major_minor_version",
|
||||
"get_platlib",
|
||||
"get_prefixed_libs",
|
||||
"get_purelib",
|
||||
"get_scheme",
|
||||
"get_src_prefix",
|
||||
"site_packages",
|
||||
"user_site",
|
||||
'USER_CACHE_DIR',
|
||||
'get_bin_prefix',
|
||||
'get_bin_user',
|
||||
'get_major_minor_version',
|
||||
'get_platlib',
|
||||
'get_prefixed_libs',
|
||||
'get_purelib',
|
||||
'get_scheme',
|
||||
'get_src_prefix',
|
||||
'site_packages',
|
||||
'user_site',
|
||||
]
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
_PLATLIBDIR: str = getattr(sys, "platlibdir", "lib")
|
||||
_PLATLIBDIR: str = getattr(sys, 'platlibdir', 'lib')
|
||||
|
||||
_USE_SYSCONFIG_DEFAULT = sys.version_info >= (3, 10)
|
||||
|
||||
|
|
@ -55,7 +62,7 @@ def _should_use_sysconfig() -> bool:
|
|||
This is a function for testability, but should be constant during any one
|
||||
run.
|
||||
"""
|
||||
return bool(getattr(sysconfig, "_PIP_USE_SYSCONFIG", _USE_SYSCONFIG_DEFAULT))
|
||||
return bool(getattr(sysconfig, '_PIP_USE_SYSCONFIG', _USE_SYSCONFIG_DEFAULT))
|
||||
|
||||
|
||||
_USE_SYSCONFIG = _should_use_sysconfig()
|
||||
|
|
@ -76,20 +83,20 @@ def _looks_like_bpo_44860() -> bool:
|
|||
from distutils.command.install import INSTALL_SCHEMES # type: ignore
|
||||
|
||||
try:
|
||||
unix_user_platlib = INSTALL_SCHEMES["unix_user"]["platlib"]
|
||||
unix_user_platlib = INSTALL_SCHEMES['unix_user']['platlib']
|
||||
except KeyError:
|
||||
return False
|
||||
return unix_user_platlib == "$usersite"
|
||||
return unix_user_platlib == '$usersite'
|
||||
|
||||
|
||||
def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool:
|
||||
platlib = scheme["platlib"]
|
||||
if "/$platlibdir/" in platlib:
|
||||
platlib = platlib.replace("/$platlibdir/", f"/{_PLATLIBDIR}/")
|
||||
if "/lib64/" not in platlib:
|
||||
def _looks_like_red_hat_patched_platlib_purelib(scheme: dict[str, str]) -> bool:
|
||||
platlib = scheme['platlib']
|
||||
if '/$platlibdir/' in platlib:
|
||||
platlib = platlib.replace('/$platlibdir/', f'/{_PLATLIBDIR}/')
|
||||
if '/lib64/' not in platlib:
|
||||
return False
|
||||
unpatched = platlib.replace("/lib64/", "/lib/")
|
||||
return unpatched.replace("$platbase/", "$base/") == scheme["purelib"]
|
||||
unpatched = platlib.replace('/lib64/', '/lib/')
|
||||
return unpatched.replace('$platbase/', '$base/') == scheme['purelib']
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
|
|
@ -101,9 +108,9 @@ def _looks_like_red_hat_lib() -> bool:
|
|||
from distutils.command.install import INSTALL_SCHEMES # type: ignore
|
||||
|
||||
return all(
|
||||
k in INSTALL_SCHEMES
|
||||
and _looks_like_red_hat_patched_platlib_purelib(INSTALL_SCHEMES[k])
|
||||
for k in ("unix_prefix", "unix_home")
|
||||
k in INSTALL_SCHEMES and
|
||||
_looks_like_red_hat_patched_platlib_purelib(INSTALL_SCHEMES[k])
|
||||
for k in ('unix_prefix', 'unix_home')
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -112,7 +119,7 @@ def _looks_like_debian_scheme() -> bool:
|
|||
"""Debian adds two additional schemes."""
|
||||
from distutils.command.install import INSTALL_SCHEMES # type: ignore
|
||||
|
||||
return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES
|
||||
return 'deb_system' in INSTALL_SCHEMES and 'unix_local' in INSTALL_SCHEMES
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
|
|
@ -130,8 +137,8 @@ def _looks_like_red_hat_scheme() -> bool:
|
|||
cmd: Any = install(Distribution())
|
||||
cmd.finalize_options()
|
||||
return (
|
||||
cmd.exec_prefix == f"{os.path.normpath(sys.exec_prefix)}/local"
|
||||
and cmd.prefix == f"{os.path.normpath(sys.prefix)}/local"
|
||||
cmd.exec_prefix == f'{os.path.normpath(sys.exec_prefix)}/local' and
|
||||
cmd.prefix == f'{os.path.normpath(sys.prefix)}/local'
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -145,10 +152,10 @@ def _looks_like_slackware_scheme() -> bool:
|
|||
if user_site is None: # User-site not available.
|
||||
return False
|
||||
try:
|
||||
paths = sysconfig.get_paths(scheme="posix_user", expand=False)
|
||||
paths = sysconfig.get_paths(scheme='posix_user', expand=False)
|
||||
except KeyError: # User-site not available.
|
||||
return False
|
||||
return "/lib64/" in paths["purelib"] and "/lib64/" not in user_site
|
||||
return '/lib64/' in paths['purelib'] and '/lib64/' not in user_site
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
|
|
@ -162,16 +169,16 @@ def _looks_like_msys2_mingw_scheme() -> bool:
|
|||
MSYS2 MINGW's patch uses lowercase ``"lib"`` instead of the usual uppercase,
|
||||
and is missing the final ``"site-packages"``.
|
||||
"""
|
||||
paths = sysconfig.get_paths("nt", expand=False)
|
||||
paths = sysconfig.get_paths('nt', expand=False)
|
||||
return all(
|
||||
"Lib" not in p and "lib" in p and not p.endswith("site-packages")
|
||||
for p in (paths[key] for key in ("platlib", "purelib"))
|
||||
'Lib' not in p and 'lib' in p and not p.endswith('site-packages')
|
||||
for p in (paths[key] for key in ('platlib', 'purelib'))
|
||||
)
|
||||
|
||||
|
||||
def _fix_abiflags(parts: Tuple[str]) -> Iterator[str]:
|
||||
ldversion = sysconfig.get_config_var("LDVERSION")
|
||||
abiflags: str = getattr(sys, "abiflags", None)
|
||||
def _fix_abiflags(parts: tuple[str]) -> Iterator[str]:
|
||||
ldversion = sysconfig.get_config_var('LDVERSION')
|
||||
abiflags: str = getattr(sys, 'abiflags', None)
|
||||
|
||||
# LDVERSION does not end with sys.abiflags. Just return the path unchanged.
|
||||
if not ldversion or not abiflags or not ldversion.endswith(abiflags):
|
||||
|
|
@ -187,11 +194,11 @@ def _fix_abiflags(parts: Tuple[str]) -> Iterator[str]:
|
|||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _warn_mismatched(old: pathlib.Path, new: pathlib.Path, *, key: str) -> None:
|
||||
issue_url = "https://github.com/pypa/pip/issues/10151"
|
||||
issue_url = 'https://github.com/pypa/pip/issues/10151'
|
||||
message = (
|
||||
"Value for %s does not match. Please report this to <%s>"
|
||||
"\ndistutils: %s"
|
||||
"\nsysconfig: %s"
|
||||
'Value for %s does not match. Please report this to <%s>'
|
||||
'\ndistutils: %s'
|
||||
'\nsysconfig: %s'
|
||||
)
|
||||
logger.log(_MISMATCH_LEVEL, message, key, issue_url, old, new)
|
||||
|
||||
|
|
@ -207,28 +214,28 @@ def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool
|
|||
def _log_context(
|
||||
*,
|
||||
user: bool = False,
|
||||
home: Optional[str] = None,
|
||||
root: Optional[str] = None,
|
||||
prefix: Optional[str] = None,
|
||||
home: str | None = None,
|
||||
root: str | None = None,
|
||||
prefix: str | None = None,
|
||||
) -> None:
|
||||
parts = [
|
||||
"Additional context:",
|
||||
"user = %r",
|
||||
"home = %r",
|
||||
"root = %r",
|
||||
"prefix = %r",
|
||||
'Additional context:',
|
||||
'user = %r',
|
||||
'home = %r',
|
||||
'root = %r',
|
||||
'prefix = %r',
|
||||
]
|
||||
|
||||
logger.log(_MISMATCH_LEVEL, "\n".join(parts), user, home, root, prefix)
|
||||
logger.log(_MISMATCH_LEVEL, '\n'.join(parts), user, home, root, prefix)
|
||||
|
||||
|
||||
def get_scheme(
|
||||
dist_name: str,
|
||||
user: bool = False,
|
||||
home: Optional[str] = None,
|
||||
root: Optional[str] = None,
|
||||
home: str | None = None,
|
||||
root: str | None = None,
|
||||
isolated: bool = False,
|
||||
prefix: Optional[str] = None,
|
||||
prefix: str | None = None,
|
||||
) -> Scheme:
|
||||
new = _sysconfig.get_scheme(
|
||||
dist_name,
|
||||
|
|
@ -263,12 +270,12 @@ def get_scheme(
|
|||
# directory name to be ``pypy`` instead. So we treat this as a bug fix
|
||||
# and not warn about it. See bpo-43307 and python/cpython#24628.
|
||||
skip_pypy_special_case = (
|
||||
sys.implementation.name == "pypy"
|
||||
and home is not None
|
||||
and k in ("platlib", "purelib")
|
||||
and old_v.parent == new_v.parent
|
||||
and old_v.name.startswith("python")
|
||||
and new_v.name.startswith("pypy")
|
||||
sys.implementation.name == 'pypy' and
|
||||
home is not None and
|
||||
k in ('platlib', 'purelib') and
|
||||
old_v.parent == new_v.parent and
|
||||
old_v.name.startswith('python') and
|
||||
new_v.name.startswith('pypy')
|
||||
)
|
||||
if skip_pypy_special_case:
|
||||
continue
|
||||
|
|
@ -277,18 +284,18 @@ def get_scheme(
|
|||
# the ``include`` value, but distutils's ``headers`` does. We'll let
|
||||
# CPython decide whether this is a bug or feature. See bpo-43948.
|
||||
skip_osx_framework_user_special_case = (
|
||||
user
|
||||
and is_osx_framework()
|
||||
and k == "headers"
|
||||
and old_v.parent.parent == new_v.parent
|
||||
and old_v.parent.name.startswith("python")
|
||||
user and
|
||||
is_osx_framework() and
|
||||
k == 'headers' and
|
||||
old_v.parent.parent == new_v.parent and
|
||||
old_v.parent.name.startswith('python')
|
||||
)
|
||||
if skip_osx_framework_user_special_case:
|
||||
continue
|
||||
|
||||
# On Red Hat and derived Linux distributions, distutils is patched to
|
||||
# use "lib64" instead of "lib" for platlib.
|
||||
if k == "platlib" and _looks_like_red_hat_lib():
|
||||
if k == 'platlib' and _looks_like_red_hat_lib():
|
||||
continue
|
||||
|
||||
# On Python 3.9+, sysconfig's posix_user scheme sets platlib against
|
||||
|
|
@ -296,12 +303,12 @@ def get_scheme(
|
|||
# using the same $usersite for both platlib and purelib. This creates a
|
||||
# mismatch when sys.platlibdir is not "lib".
|
||||
skip_bpo_44860 = (
|
||||
user
|
||||
and k == "platlib"
|
||||
and not WINDOWS
|
||||
and sys.version_info >= (3, 9)
|
||||
and _PLATLIBDIR != "lib"
|
||||
and _looks_like_bpo_44860()
|
||||
user and
|
||||
k == 'platlib' and
|
||||
not WINDOWS and
|
||||
sys.version_info >= (3, 9) and
|
||||
_PLATLIBDIR != 'lib' and
|
||||
_looks_like_bpo_44860()
|
||||
)
|
||||
if skip_bpo_44860:
|
||||
continue
|
||||
|
|
@ -309,10 +316,10 @@ def get_scheme(
|
|||
# Slackware incorrectly patches posix_user to use lib64 instead of lib,
|
||||
# but not usersite to match the location.
|
||||
skip_slackware_user_scheme = (
|
||||
user
|
||||
and k in ("platlib", "purelib")
|
||||
and not WINDOWS
|
||||
and _looks_like_slackware_scheme()
|
||||
user and
|
||||
k in ('platlib', 'purelib') and
|
||||
not WINDOWS and
|
||||
_looks_like_slackware_scheme()
|
||||
)
|
||||
if skip_slackware_user_scheme:
|
||||
continue
|
||||
|
|
@ -321,12 +328,12 @@ def get_scheme(
|
|||
# /usr/local instead of /usr. Debian also places lib in dist-packages
|
||||
# instead of site-packages, but the /usr/local check should cover it.
|
||||
skip_linux_system_special_case = (
|
||||
not (user or home or prefix or running_under_virtualenv())
|
||||
and old_v.parts[1:3] == ("usr", "local")
|
||||
and len(new_v.parts) > 1
|
||||
and new_v.parts[1] == "usr"
|
||||
and (len(new_v.parts) < 3 or new_v.parts[2] != "local")
|
||||
and (_looks_like_red_hat_scheme() or _looks_like_debian_scheme())
|
||||
not (user or home or prefix or running_under_virtualenv()) and
|
||||
old_v.parts[1:3] == ('usr', 'local') and
|
||||
len(new_v.parts) > 1 and
|
||||
new_v.parts[1] == 'usr' and
|
||||
(len(new_v.parts) < 3 or new_v.parts[2] != 'local') and
|
||||
(_looks_like_red_hat_scheme() or _looks_like_debian_scheme())
|
||||
)
|
||||
if skip_linux_system_special_case:
|
||||
continue
|
||||
|
|
@ -334,10 +341,10 @@ def get_scheme(
|
|||
# On Python 3.7 and earlier, sysconfig does not include sys.abiflags in
|
||||
# the "pythonX.Y" part of the path, but distutils does.
|
||||
skip_sysconfig_abiflag_bug = (
|
||||
sys.version_info < (3, 8)
|
||||
and not WINDOWS
|
||||
and k in ("headers", "platlib", "purelib")
|
||||
and tuple(_fix_abiflags(old_v.parts)) == new_v.parts
|
||||
sys.version_info < (3, 8) and
|
||||
not WINDOWS and
|
||||
k in ('headers', 'platlib', 'purelib') and
|
||||
tuple(_fix_abiflags(old_v.parts)) == new_v.parts
|
||||
)
|
||||
if skip_sysconfig_abiflag_bug:
|
||||
continue
|
||||
|
|
@ -345,7 +352,7 @@ def get_scheme(
|
|||
# MSYS2 MINGW's sysconfig patch does not include the "site-packages"
|
||||
# part of the path. This is incorrect and will be fixed in MSYS.
|
||||
skip_msys2_mingw_bug = (
|
||||
WINDOWS and k in ("platlib", "purelib") and _looks_like_msys2_mingw_scheme()
|
||||
WINDOWS and k in ('platlib', 'purelib') and _looks_like_msys2_mingw_scheme()
|
||||
)
|
||||
if skip_msys2_mingw_bug:
|
||||
continue
|
||||
|
|
@ -355,14 +362,14 @@ def get_scheme(
|
|||
# triggers special logic in sysconfig that's not present in distutils.
|
||||
# https://github.com/python/cpython/blob/8c21941ddaf/Lib/sysconfig.py#L178-L194
|
||||
skip_cpython_build = (
|
||||
sysconfig.is_python_build(check_home=True)
|
||||
and not WINDOWS
|
||||
and k in ("headers", "include", "platinclude")
|
||||
sysconfig.is_python_build(check_home=True) and
|
||||
not WINDOWS and
|
||||
k in ('headers', 'include', 'platinclude')
|
||||
)
|
||||
if skip_cpython_build:
|
||||
continue
|
||||
|
||||
warning_contexts.append((old_v, new_v, f"scheme.{k}"))
|
||||
warning_contexts.append((old_v, new_v, f'scheme.{k}'))
|
||||
|
||||
if not warning_contexts:
|
||||
return old
|
||||
|
|
@ -382,10 +389,10 @@ def get_scheme(
|
|||
if any(default_old[k] != getattr(old, k) for k in SCHEME_KEYS):
|
||||
deprecated(
|
||||
reason=(
|
||||
"Configuring installation scheme with distutils config files "
|
||||
"is deprecated and will no longer work in the near future. If you "
|
||||
"are using a Homebrew or Linuxbrew Python, please see discussion "
|
||||
"at https://github.com/Homebrew/homebrew-core/issues/76621"
|
||||
'Configuring installation scheme with distutils config files '
|
||||
'is deprecated and will no longer work in the near future. If you '
|
||||
'are using a Homebrew or Linuxbrew Python, please see discussion '
|
||||
'at https://github.com/Homebrew/homebrew-core/issues/76621'
|
||||
),
|
||||
replacement=None,
|
||||
gone_in=None,
|
||||
|
|
@ -406,13 +413,13 @@ def get_bin_prefix() -> str:
|
|||
return new
|
||||
|
||||
old = _distutils.get_bin_prefix()
|
||||
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="bin_prefix"):
|
||||
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key='bin_prefix'):
|
||||
_log_context()
|
||||
return old
|
||||
|
||||
|
||||
def get_bin_user() -> str:
|
||||
return _sysconfig.get_scheme("", user=True).scripts
|
||||
return _sysconfig.get_scheme('', user=True).scripts
|
||||
|
||||
|
||||
def _looks_like_deb_system_dist_packages(value: str) -> bool:
|
||||
|
|
@ -427,7 +434,7 @@ def _looks_like_deb_system_dist_packages(value: str) -> bool:
|
|||
"""
|
||||
if not _looks_like_debian_scheme():
|
||||
return False
|
||||
if value == "/usr/lib/python3/dist-packages":
|
||||
if value == '/usr/lib/python3/dist-packages':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -441,7 +448,7 @@ def get_purelib() -> str:
|
|||
old = _distutils.get_purelib()
|
||||
if _looks_like_deb_system_dist_packages(old):
|
||||
return old
|
||||
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="purelib"):
|
||||
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key='purelib'):
|
||||
_log_context()
|
||||
return old
|
||||
|
||||
|
|
@ -455,12 +462,12 @@ def get_platlib() -> str:
|
|||
old = _distutils.get_platlib()
|
||||
if _looks_like_deb_system_dist_packages(old):
|
||||
return old
|
||||
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="platlib"):
|
||||
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key='platlib'):
|
||||
_log_context()
|
||||
return old
|
||||
|
||||
|
||||
def _deduplicated(v1: str, v2: str) -> List[str]:
|
||||
def _deduplicated(v1: str, v2: str) -> list[str]:
|
||||
"""Deduplicate values from a list."""
|
||||
if v1 == v2:
|
||||
return [v1]
|
||||
|
|
@ -469,12 +476,12 @@ def _deduplicated(v1: str, v2: str) -> List[str]:
|
|||
|
||||
def _looks_like_apple_library(path: str) -> bool:
|
||||
"""Apple patches sysconfig to *always* look under */Library/Python*."""
|
||||
if sys.platform[:6] != "darwin":
|
||||
if sys.platform[:6] != 'darwin':
|
||||
return False
|
||||
return path == f"/Library/Python/{get_major_minor_version()}/site-packages"
|
||||
return path == f'/Library/Python/{get_major_minor_version()}/site-packages'
|
||||
|
||||
|
||||
def get_prefixed_libs(prefix: str) -> List[str]:
|
||||
def get_prefixed_libs(prefix: str) -> list[str]:
|
||||
"""Return the lib locations under ``prefix``."""
|
||||
new_pure, new_plat = _sysconfig.get_prefixed_libs(prefix)
|
||||
if _USE_SYSCONFIG:
|
||||
|
|
@ -493,9 +500,9 @@ def get_prefixed_libs(prefix: str) -> List[str]:
|
|||
reason=(
|
||||
"Python distributed by Apple's Command Line Tools incorrectly "
|
||||
"patches sysconfig to always point to '/Library/Python'. This "
|
||||
"will cause build isolation to operate incorrectly on Python "
|
||||
"3.10 or later. Please help report this to Apple so they can "
|
||||
"fix this. https://developer.apple.com/bug-reporting/"
|
||||
'will cause build isolation to operate incorrectly on Python '
|
||||
'3.10 or later. Please help report this to Apple so they can '
|
||||
'fix this. https://developer.apple.com/bug-reporting/'
|
||||
),
|
||||
replacement=None,
|
||||
gone_in=None,
|
||||
|
|
@ -506,12 +513,12 @@ def get_prefixed_libs(prefix: str) -> List[str]:
|
|||
_warn_if_mismatch(
|
||||
pathlib.Path(old_pure),
|
||||
pathlib.Path(new_pure),
|
||||
key="prefixed-purelib",
|
||||
key='prefixed-purelib',
|
||||
),
|
||||
_warn_if_mismatch(
|
||||
pathlib.Path(old_plat),
|
||||
pathlib.Path(new_plat),
|
||||
key="prefixed-platlib",
|
||||
key='prefixed-platlib',
|
||||
),
|
||||
]
|
||||
if any(warned):
|
||||
|
|
|
|||
|
|
@ -1,17 +1,22 @@
|
|||
"""Locations where we look for configs, install stuff, etc"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from distutils.cmd import Command as DistutilsCommand
|
||||
from distutils.command.install import SCHEME_KEYS
|
||||
from distutils.command.install import install as distutils_install_command
|
||||
from distutils.sysconfig import get_python_lib
|
||||
from typing import Dict, List, Optional, Tuple, Union, cast
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from distutils.cmd import Command as DistutilsCommand
|
||||
from distutils.command.install import install as distutils_install_command
|
||||
from distutils.command.install import SCHEME_KEYS
|
||||
from distutils.sysconfig import get_python_lib
|
||||
from pip._internal.models.scheme import Scheme
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
|
|
@ -30,15 +35,15 @@ def distutils_scheme(
|
|||
prefix: str = None,
|
||||
*,
|
||||
ignore_config_files: bool = False,
|
||||
) -> Dict[str, str]:
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
Return a distutils install scheme
|
||||
"""
|
||||
from distutils.dist import Distribution
|
||||
|
||||
dist_args: Dict[str, Union[str, List[str]]] = {"name": dist_name}
|
||||
dist_args: dict[str, str | list[str]] = {'name': dist_name}
|
||||
if isolated:
|
||||
dist_args["script_args"] = ["--no-user-cfg"]
|
||||
dist_args['script_args'] = ['--no-user-cfg']
|
||||
|
||||
d = Distribution(dist_args)
|
||||
if not ignore_config_files:
|
||||
|
|
@ -48,21 +53,21 @@ def distutils_scheme(
|
|||
# Typeshed does not include find_config_files() for some reason.
|
||||
paths = d.find_config_files() # type: ignore
|
||||
logger.warning(
|
||||
"Ignore distutils configs in %s due to encoding errors.",
|
||||
", ".join(os.path.basename(p) for p in paths),
|
||||
'Ignore distutils configs in %s due to encoding errors.',
|
||||
', '.join(os.path.basename(p) for p in paths),
|
||||
)
|
||||
obj: Optional[DistutilsCommand] = None
|
||||
obj = d.get_command_obj("install", create=True)
|
||||
obj: DistutilsCommand | None = None
|
||||
obj = d.get_command_obj('install', create=True)
|
||||
assert obj is not None
|
||||
i = cast(distutils_install_command, obj)
|
||||
# NOTE: setting user or home has the side-effect of creating the home dir
|
||||
# or user base for installations during finalize_options()
|
||||
# ideally, we'd prefer a scheme class that has no side-effects.
|
||||
assert not (user and prefix), f"user={user} prefix={prefix}"
|
||||
assert not (home and prefix), f"home={home} prefix={prefix}"
|
||||
assert not (user and prefix), f'user={user} prefix={prefix}'
|
||||
assert not (home and prefix), f'home={home} prefix={prefix}'
|
||||
i.user = user or i.user
|
||||
if user or home:
|
||||
i.prefix = ""
|
||||
i.prefix = ''
|
||||
i.prefix = prefix or i.prefix
|
||||
i.home = home or i.home
|
||||
i.root = root or i.root
|
||||
|
|
@ -70,14 +75,14 @@ def distutils_scheme(
|
|||
|
||||
scheme = {}
|
||||
for key in SCHEME_KEYS:
|
||||
scheme[key] = getattr(i, "install_" + key)
|
||||
scheme[key] = getattr(i, 'install_' + key)
|
||||
|
||||
# install_lib specified in setup.cfg should install *everything*
|
||||
# into there (i.e. it takes precedence over both purelib and
|
||||
# platlib). Note, i.install_lib is *always* set after
|
||||
# finalize_options(); we only want to override here if the user
|
||||
# has explicitly requested it hence going back to the config
|
||||
if "install_lib" in d.get_option_dict("install"):
|
||||
if 'install_lib' in d.get_option_dict('install'):
|
||||
scheme.update(dict(purelib=i.install_lib, platlib=i.install_lib))
|
||||
|
||||
if running_under_virtualenv():
|
||||
|
|
@ -87,17 +92,17 @@ def distutils_scheme(
|
|||
prefix = i.install_userbase # type: ignore
|
||||
else:
|
||||
prefix = i.prefix
|
||||
scheme["headers"] = os.path.join(
|
||||
scheme['headers'] = os.path.join(
|
||||
prefix,
|
||||
"include",
|
||||
"site",
|
||||
f"python{get_major_minor_version()}",
|
||||
'include',
|
||||
'site',
|
||||
f'python{get_major_minor_version()}',
|
||||
dist_name,
|
||||
)
|
||||
|
||||
if root is not None:
|
||||
path_no_drive = os.path.splitdrive(os.path.abspath(scheme["headers"]))[1]
|
||||
scheme["headers"] = os.path.join(root, path_no_drive[1:])
|
||||
path_no_drive = os.path.splitdrive(os.path.abspath(scheme['headers']))[1]
|
||||
scheme['headers'] = os.path.join(root, path_no_drive[1:])
|
||||
|
||||
return scheme
|
||||
|
||||
|
|
@ -105,10 +110,10 @@ def distutils_scheme(
|
|||
def get_scheme(
|
||||
dist_name: str,
|
||||
user: bool = False,
|
||||
home: Optional[str] = None,
|
||||
root: Optional[str] = None,
|
||||
home: str | None = None,
|
||||
root: str | None = None,
|
||||
isolated: bool = False,
|
||||
prefix: Optional[str] = None,
|
||||
prefix: str | None = None,
|
||||
) -> Scheme:
|
||||
"""
|
||||
Get the "scheme" corresponding to the input parameters. The distutils
|
||||
|
|
@ -129,11 +134,11 @@ def get_scheme(
|
|||
"""
|
||||
scheme = distutils_scheme(dist_name, user, home, root, isolated, prefix)
|
||||
return Scheme(
|
||||
platlib=scheme["platlib"],
|
||||
purelib=scheme["purelib"],
|
||||
headers=scheme["headers"],
|
||||
scripts=scheme["scripts"],
|
||||
data=scheme["data"],
|
||||
platlib=scheme['platlib'],
|
||||
purelib=scheme['purelib'],
|
||||
headers=scheme['headers'],
|
||||
scripts=scheme['scripts'],
|
||||
data=scheme['data'],
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -142,16 +147,16 @@ def get_bin_prefix() -> str:
|
|||
# so we need to call normpath to eliminate them.
|
||||
prefix = os.path.normpath(sys.prefix)
|
||||
if WINDOWS:
|
||||
bin_py = os.path.join(prefix, "Scripts")
|
||||
bin_py = os.path.join(prefix, 'Scripts')
|
||||
# buildout uses 'bin' on Windows too?
|
||||
if not os.path.exists(bin_py):
|
||||
bin_py = os.path.join(prefix, "bin")
|
||||
bin_py = os.path.join(prefix, 'bin')
|
||||
return bin_py
|
||||
# Forcing to use /usr/local/bin for standard macOS framework installs
|
||||
# Also log to ~/Library/Logs/ for use with the Console.app log viewer
|
||||
if sys.platform[:6] == "darwin" and prefix[:16] == "/System/Library/":
|
||||
return "/usr/local/bin"
|
||||
return os.path.join(prefix, "bin")
|
||||
if sys.platform[:6] == 'darwin' and prefix[:16] == '/System/Library/':
|
||||
return '/usr/local/bin'
|
||||
return os.path.join(prefix, 'bin')
|
||||
|
||||
|
||||
def get_purelib() -> str:
|
||||
|
|
@ -162,7 +167,7 @@ def get_platlib() -> str:
|
|||
return get_python_lib(plat_specific=True)
|
||||
|
||||
|
||||
def get_prefixed_libs(prefix: str) -> Tuple[str, str]:
|
||||
def get_prefixed_libs(prefix: str) -> tuple[str, str]:
|
||||
return (
|
||||
get_python_lib(plat_specific=False, prefix=prefix),
|
||||
get_python_lib(plat_specific=True, prefix=prefix),
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
import distutils.util # FIXME: For change_root.
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import sysconfig
|
||||
import typing
|
||||
|
||||
from pip._internal.exceptions import InvalidSchemeCombination, UserInstallationInvalid
|
||||
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
|
||||
import distutils.util # FIXME: For change_root.
|
||||
from pip._internal.exceptions import InvalidSchemeCombination
|
||||
from pip._internal.exceptions import UserInstallationInvalid
|
||||
from pip._internal.models.scheme import Scheme
|
||||
from pip._internal.models.scheme import SCHEME_KEYS
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
|
||||
from .base import get_major_minor_version, is_osx_framework
|
||||
from .base import get_major_minor_version
|
||||
from .base import is_osx_framework
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -24,7 +29,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
_AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names())
|
||||
|
||||
_PREFERRED_SCHEME_API = getattr(sysconfig, "get_preferred_scheme", None)
|
||||
_PREFERRED_SCHEME_API = getattr(sysconfig, 'get_preferred_scheme', None)
|
||||
|
||||
|
||||
def _should_use_osx_framework_prefix() -> bool:
|
||||
|
|
@ -47,9 +52,9 @@ def _should_use_osx_framework_prefix() -> bool:
|
|||
or our own, and we deal with this special case in ``get_scheme()`` instead.
|
||||
"""
|
||||
return (
|
||||
"osx_framework_library" in _AVAILABLE_SCHEMES
|
||||
and not running_under_virtualenv()
|
||||
and is_osx_framework()
|
||||
'osx_framework_library' in _AVAILABLE_SCHEMES and
|
||||
not running_under_virtualenv() and
|
||||
is_osx_framework()
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -68,67 +73,67 @@ def _infer_prefix() -> str:
|
|||
If none of the above works, fall back to ``posix_prefix``.
|
||||
"""
|
||||
if _PREFERRED_SCHEME_API:
|
||||
return _PREFERRED_SCHEME_API("prefix")
|
||||
return _PREFERRED_SCHEME_API('prefix')
|
||||
if _should_use_osx_framework_prefix():
|
||||
return "osx_framework_library"
|
||||
implementation_suffixed = f"{sys.implementation.name}_{os.name}"
|
||||
return 'osx_framework_library'
|
||||
implementation_suffixed = f'{sys.implementation.name}_{os.name}'
|
||||
if implementation_suffixed in _AVAILABLE_SCHEMES:
|
||||
return implementation_suffixed
|
||||
if sys.implementation.name in _AVAILABLE_SCHEMES:
|
||||
return sys.implementation.name
|
||||
suffixed = f"{os.name}_prefix"
|
||||
suffixed = f'{os.name}_prefix'
|
||||
if suffixed in _AVAILABLE_SCHEMES:
|
||||
return suffixed
|
||||
if os.name in _AVAILABLE_SCHEMES: # On Windows, prefx is just called "nt".
|
||||
return os.name
|
||||
return "posix_prefix"
|
||||
return 'posix_prefix'
|
||||
|
||||
|
||||
def _infer_user() -> str:
|
||||
"""Try to find a user scheme for the current platform."""
|
||||
if _PREFERRED_SCHEME_API:
|
||||
return _PREFERRED_SCHEME_API("user")
|
||||
return _PREFERRED_SCHEME_API('user')
|
||||
if is_osx_framework() and not running_under_virtualenv():
|
||||
suffixed = "osx_framework_user"
|
||||
suffixed = 'osx_framework_user'
|
||||
else:
|
||||
suffixed = f"{os.name}_user"
|
||||
suffixed = f'{os.name}_user'
|
||||
if suffixed in _AVAILABLE_SCHEMES:
|
||||
return suffixed
|
||||
if "posix_user" not in _AVAILABLE_SCHEMES: # User scheme unavailable.
|
||||
if 'posix_user' not in _AVAILABLE_SCHEMES: # User scheme unavailable.
|
||||
raise UserInstallationInvalid()
|
||||
return "posix_user"
|
||||
return 'posix_user'
|
||||
|
||||
|
||||
def _infer_home() -> str:
|
||||
"""Try to find a home for the current platform."""
|
||||
if _PREFERRED_SCHEME_API:
|
||||
return _PREFERRED_SCHEME_API("home")
|
||||
suffixed = f"{os.name}_home"
|
||||
return _PREFERRED_SCHEME_API('home')
|
||||
suffixed = f'{os.name}_home'
|
||||
if suffixed in _AVAILABLE_SCHEMES:
|
||||
return suffixed
|
||||
return "posix_home"
|
||||
return 'posix_home'
|
||||
|
||||
|
||||
# Update these keys if the user sets a custom home.
|
||||
_HOME_KEYS = [
|
||||
"installed_base",
|
||||
"base",
|
||||
"installed_platbase",
|
||||
"platbase",
|
||||
"prefix",
|
||||
"exec_prefix",
|
||||
'installed_base',
|
||||
'base',
|
||||
'installed_platbase',
|
||||
'platbase',
|
||||
'prefix',
|
||||
'exec_prefix',
|
||||
]
|
||||
if sysconfig.get_config_var("userbase") is not None:
|
||||
_HOME_KEYS.append("userbase")
|
||||
if sysconfig.get_config_var('userbase') is not None:
|
||||
_HOME_KEYS.append('userbase')
|
||||
|
||||
|
||||
def get_scheme(
|
||||
dist_name: str,
|
||||
user: bool = False,
|
||||
home: typing.Optional[str] = None,
|
||||
root: typing.Optional[str] = None,
|
||||
home: str | None = None,
|
||||
root: str | None = None,
|
||||
isolated: bool = False,
|
||||
prefix: typing.Optional[str] = None,
|
||||
prefix: str | None = None,
|
||||
) -> Scheme:
|
||||
"""
|
||||
Get the "scheme" corresponding to the input parameters.
|
||||
|
|
@ -144,9 +149,9 @@ def get_scheme(
|
|||
base directory for the same
|
||||
"""
|
||||
if user and prefix:
|
||||
raise InvalidSchemeCombination("--user", "--prefix")
|
||||
raise InvalidSchemeCombination('--user', '--prefix')
|
||||
if home and prefix:
|
||||
raise InvalidSchemeCombination("--home", "--prefix")
|
||||
raise InvalidSchemeCombination('--home', '--prefix')
|
||||
|
||||
if home is not None:
|
||||
scheme_name = _infer_home()
|
||||
|
|
@ -158,8 +163,8 @@ def get_scheme(
|
|||
# Special case: When installing into a custom prefix, use posix_prefix
|
||||
# instead of osx_framework_library. See _should_use_osx_framework_prefix()
|
||||
# docstring for details.
|
||||
if prefix is not None and scheme_name == "osx_framework_library":
|
||||
scheme_name = "posix_prefix"
|
||||
if prefix is not None and scheme_name == 'osx_framework_library':
|
||||
scheme_name = 'posix_prefix'
|
||||
|
||||
if home is not None:
|
||||
variables = {k: home for k in _HOME_KEYS}
|
||||
|
|
@ -177,20 +182,20 @@ def get_scheme(
|
|||
# pip's historical header path logic (see point 1) did not do this.
|
||||
if running_under_virtualenv():
|
||||
if user:
|
||||
base = variables.get("userbase", sys.prefix)
|
||||
base = variables.get('userbase', sys.prefix)
|
||||
else:
|
||||
base = variables.get("base", sys.prefix)
|
||||
python_xy = f"python{get_major_minor_version()}"
|
||||
paths["include"] = os.path.join(base, "include", "site", python_xy)
|
||||
base = variables.get('base', sys.prefix)
|
||||
python_xy = f'python{get_major_minor_version()}'
|
||||
paths['include'] = os.path.join(base, 'include', 'site', python_xy)
|
||||
elif not dist_name:
|
||||
dist_name = "UNKNOWN"
|
||||
dist_name = 'UNKNOWN'
|
||||
|
||||
scheme = Scheme(
|
||||
platlib=paths["platlib"],
|
||||
purelib=paths["purelib"],
|
||||
headers=os.path.join(paths["include"], dist_name),
|
||||
scripts=paths["scripts"],
|
||||
data=paths["data"],
|
||||
platlib=paths['platlib'],
|
||||
purelib=paths['purelib'],
|
||||
headers=os.path.join(paths['include'], dist_name),
|
||||
scripts=paths['scripts'],
|
||||
data=paths['data'],
|
||||
)
|
||||
if root is not None:
|
||||
for key in SCHEME_KEYS:
|
||||
|
|
@ -201,19 +206,19 @@ def get_scheme(
|
|||
|
||||
def get_bin_prefix() -> str:
|
||||
# Forcing to use /usr/local/bin for standard macOS framework installs.
|
||||
if sys.platform[:6] == "darwin" and sys.prefix[:16] == "/System/Library/":
|
||||
return "/usr/local/bin"
|
||||
return sysconfig.get_paths()["scripts"]
|
||||
if sys.platform[:6] == 'darwin' and sys.prefix[:16] == '/System/Library/':
|
||||
return '/usr/local/bin'
|
||||
return sysconfig.get_paths()['scripts']
|
||||
|
||||
|
||||
def get_purelib() -> str:
|
||||
return sysconfig.get_paths()["purelib"]
|
||||
return sysconfig.get_paths()['purelib']
|
||||
|
||||
|
||||
def get_platlib() -> str:
|
||||
return sysconfig.get_paths()["platlib"]
|
||||
return sysconfig.get_paths()['platlib']
|
||||
|
||||
|
||||
def get_prefixed_libs(prefix: str) -> typing.Tuple[str, str]:
|
||||
paths = sysconfig.get_paths(vars={"base": prefix, "platbase": prefix})
|
||||
return (paths["purelib"], paths["platlib"])
|
||||
def get_prefixed_libs(prefix: str) -> tuple[str, str]:
|
||||
paths = sysconfig.get_paths(vars={'base': prefix, 'platbase': prefix})
|
||||
return (paths['purelib'], paths['platlib'])
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import os
|
||||
import site
|
||||
|
|
@ -9,10 +11,10 @@ from pip._internal.utils import appdirs
|
|||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
|
||||
# Application Directories
|
||||
USER_CACHE_DIR = appdirs.user_cache_dir("pip")
|
||||
USER_CACHE_DIR = appdirs.user_cache_dir('pip')
|
||||
|
||||
# FIXME doesn't account for venv linked to global site-packages
|
||||
site_packages: typing.Optional[str] = sysconfig.get_path("purelib")
|
||||
site_packages: str | None = sysconfig.get_path('purelib')
|
||||
|
||||
|
||||
def get_major_minor_version() -> str:
|
||||
|
|
@ -20,19 +22,19 @@ def get_major_minor_version() -> str:
|
|||
Return the major-minor version of the current Python as a string, e.g.
|
||||
"3.7" or "3.10".
|
||||
"""
|
||||
return "{}.{}".format(*sys.version_info)
|
||||
return '{}.{}'.format(*sys.version_info)
|
||||
|
||||
|
||||
def get_src_prefix() -> str:
|
||||
if running_under_virtualenv():
|
||||
src_prefix = os.path.join(sys.prefix, "src")
|
||||
src_prefix = os.path.join(sys.prefix, 'src')
|
||||
else:
|
||||
# FIXME: keep src in cwd for now (it is not a temporary folder)
|
||||
try:
|
||||
src_prefix = os.path.join(os.getcwd(), "src")
|
||||
src_prefix = os.path.join(os.getcwd(), 'src')
|
||||
except OSError:
|
||||
# In case the current working directory has been renamed or deleted
|
||||
sys.exit("The folder you are executing pip from can no longer be found.")
|
||||
sys.exit('The folder you are executing pip from can no longer be found.')
|
||||
|
||||
# under macOS + virtualenv sys.prefix is not properly resolved
|
||||
# it is something like /path/to/python/bin/..
|
||||
|
|
@ -42,11 +44,11 @@ def get_src_prefix() -> str:
|
|||
try:
|
||||
# Use getusersitepackages if this is present, as it ensures that the
|
||||
# value is initialised properly.
|
||||
user_site: typing.Optional[str] = site.getusersitepackages()
|
||||
user_site: str | None = site.getusersitepackages()
|
||||
except AttributeError:
|
||||
user_site = site.USER_SITE
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def is_osx_framework() -> bool:
|
||||
return bool(sysconfig.get_config_var("PYTHONFRAMEWORK"))
|
||||
return bool(sysconfig.get_config_var('PYTHONFRAMEWORK'))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
from typing import List, Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def main(args: Optional[List[str]] = None) -> int:
|
||||
def main(args: list[str] | None = None) -> int:
|
||||
"""This is preserved for old console scripts that may still be referencing
|
||||
it.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
from typing import List, Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from .base import BaseDistribution
|
||||
from .base import BaseEnvironment
|
||||
from .base import FilesystemWheel
|
||||
from .base import MemoryWheel
|
||||
from .base import Wheel
|
||||
|
||||
__all__ = [
|
||||
"BaseDistribution",
|
||||
"BaseEnvironment",
|
||||
"FilesystemWheel",
|
||||
"MemoryWheel",
|
||||
"Wheel",
|
||||
"get_default_environment",
|
||||
"get_environment",
|
||||
"get_wheel_distribution",
|
||||
'BaseDistribution',
|
||||
'BaseEnvironment',
|
||||
'FilesystemWheel',
|
||||
'MemoryWheel',
|
||||
'Wheel',
|
||||
'get_default_environment',
|
||||
'get_environment',
|
||||
'get_wheel_distribution',
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -26,7 +33,7 @@ def get_default_environment() -> BaseEnvironment:
|
|||
return Environment.default()
|
||||
|
||||
|
||||
def get_environment(paths: Optional[List[str]]) -> BaseEnvironment:
|
||||
def get_environment(paths: list[str] | None) -> BaseEnvironment:
|
||||
"""Get a representation of the environment specified by ``paths``.
|
||||
|
||||
This returns an Environment instance from the chosen backend based on the
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import email.message
|
||||
import json
|
||||
|
|
@ -5,38 +7,35 @@ import logging
|
|||
import pathlib
|
||||
import re
|
||||
import zipfile
|
||||
from typing import (
|
||||
IO,
|
||||
TYPE_CHECKING,
|
||||
Collection,
|
||||
Container,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
from pip._vendor.packaging.version import LegacyVersion, Version
|
||||
from typing import Collection
|
||||
from typing import Container
|
||||
from typing import IO
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from pip._internal.exceptions import NoneMetadataError
|
||||
from pip._internal.locations import site_packages, user_site
|
||||
from pip._internal.models.direct_url import (
|
||||
DIRECT_URL_METADATA_NAME,
|
||||
DirectUrl,
|
||||
DirectUrlValidationError,
|
||||
)
|
||||
from pip._internal.locations import site_packages
|
||||
from pip._internal.locations import user_site
|
||||
from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME
|
||||
from pip._internal.models.direct_url import DirectUrl
|
||||
from pip._internal.models.direct_url import DirectUrlValidationError
|
||||
from pip._internal.utils.compat import stdlib_pkgs # TODO: Move definition here.
|
||||
from pip._internal.utils.egg_link import (
|
||||
egg_link_path_from_location,
|
||||
egg_link_path_from_sys_path,
|
||||
)
|
||||
from pip._internal.utils.misc import is_local, normalize_path
|
||||
from pip._internal.utils.egg_link import egg_link_path_from_location
|
||||
from pip._internal.utils.egg_link import egg_link_path_from_sys_path
|
||||
from pip._internal.utils.misc import is_local
|
||||
from pip._internal.utils.misc import normalize_path
|
||||
from pip._internal.utils.urls import url_to_path
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.specifiers import InvalidSpecifier
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
from pip._vendor.packaging.version import LegacyVersion
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
|
|
@ -65,8 +64,8 @@ class BaseEntryPoint(Protocol):
|
|||
|
||||
|
||||
def _convert_installed_files_path(
|
||||
entry: Tuple[str, ...],
|
||||
info: Tuple[str, ...],
|
||||
entry: tuple[str, ...],
|
||||
info: tuple[str, ...],
|
||||
) -> str:
|
||||
"""Convert a legacy installed-files.txt path into modern RECORD path.
|
||||
|
||||
|
|
@ -85,9 +84,9 @@ def _convert_installed_files_path(
|
|||
from ``info``; if ``info`` is empty, start appending ``..`` instead.
|
||||
2. Join the two directly.
|
||||
"""
|
||||
while entry and entry[0] == "..":
|
||||
if not info or info[-1] == "..":
|
||||
info += ("..",)
|
||||
while entry and entry[0] == '..':
|
||||
if not info or info[-1] == '..':
|
||||
info += ('..',)
|
||||
else:
|
||||
info = info[:-1]
|
||||
entry = entry[1:]
|
||||
|
|
@ -96,13 +95,13 @@ def _convert_installed_files_path(
|
|||
|
||||
class BaseDistribution(Protocol):
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.raw_name} {self.version} ({self.location})"
|
||||
return f'{self.raw_name} {self.version} ({self.location})'
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.raw_name} {self.version}"
|
||||
return f'{self.raw_name} {self.version}'
|
||||
|
||||
@property
|
||||
def location(self) -> Optional[str]:
|
||||
def location(self) -> str | None:
|
||||
"""Where the distribution is loaded from.
|
||||
|
||||
A string value is not necessarily a filesystem path, since distributions
|
||||
|
|
@ -116,7 +115,7 @@ class BaseDistribution(Protocol):
|
|||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def editable_project_location(self) -> Optional[str]:
|
||||
def editable_project_location(self) -> str | None:
|
||||
"""The project location for editable distributions.
|
||||
|
||||
This is the directory where pyproject.toml or setup.py is located.
|
||||
|
|
@ -138,7 +137,7 @@ class BaseDistribution(Protocol):
|
|||
return None
|
||||
|
||||
@property
|
||||
def installed_location(self) -> Optional[str]:
|
||||
def installed_location(self) -> str | None:
|
||||
"""The distribution's "installed" location.
|
||||
|
||||
This should generally be a ``site-packages`` directory. This is
|
||||
|
|
@ -158,7 +157,7 @@ class BaseDistribution(Protocol):
|
|||
return normalize_path(location)
|
||||
|
||||
@property
|
||||
def info_location(self) -> Optional[str]:
|
||||
def info_location(self) -> str | None:
|
||||
"""Location of the .[egg|dist]-info directory or file.
|
||||
|
||||
Similarly to ``location``, a string value is not necessarily a
|
||||
|
|
@ -196,7 +195,7 @@ class BaseDistribution(Protocol):
|
|||
location = self.location
|
||||
if not location:
|
||||
return False
|
||||
return location.endswith(".egg")
|
||||
return location.endswith('.egg')
|
||||
|
||||
@property
|
||||
def installed_with_setuptools_egg_info(self) -> bool:
|
||||
|
|
@ -212,7 +211,7 @@ class BaseDistribution(Protocol):
|
|||
info_location = self.info_location
|
||||
if not info_location:
|
||||
return False
|
||||
if not info_location.endswith(".egg-info"):
|
||||
if not info_location.endswith('.egg-info'):
|
||||
return False
|
||||
return pathlib.Path(info_location).is_dir()
|
||||
|
||||
|
|
@ -228,7 +227,7 @@ class BaseDistribution(Protocol):
|
|||
info_location = self.info_location
|
||||
if not info_location:
|
||||
return False
|
||||
if not info_location.endswith(".dist-info"):
|
||||
if not info_location.endswith('.dist-info'):
|
||||
return False
|
||||
return pathlib.Path(info_location).is_dir()
|
||||
|
||||
|
|
@ -246,10 +245,10 @@ class BaseDistribution(Protocol):
|
|||
|
||||
This is a copy of ``pkg_resources.to_filename()`` for compatibility.
|
||||
"""
|
||||
return self.raw_name.replace("-", "_")
|
||||
return self.raw_name.replace('-', '_')
|
||||
|
||||
@property
|
||||
def direct_url(self) -> Optional[DirectUrl]:
|
||||
def direct_url(self) -> DirectUrl | None:
|
||||
"""Obtain a DirectUrl from this distribution.
|
||||
|
||||
Returns None if the distribution has no `direct_url.json` metadata,
|
||||
|
|
@ -267,7 +266,7 @@ class BaseDistribution(Protocol):
|
|||
DirectUrlValidationError,
|
||||
) as e:
|
||||
logger.warning(
|
||||
"Error parsing %s for %s: %s",
|
||||
'Error parsing %s for %s: %s',
|
||||
DIRECT_URL_METADATA_NAME,
|
||||
self.canonical_name,
|
||||
e,
|
||||
|
|
@ -277,14 +276,14 @@ class BaseDistribution(Protocol):
|
|||
@property
|
||||
def installer(self) -> str:
|
||||
try:
|
||||
installer_text = self.read_text("INSTALLER")
|
||||
installer_text = self.read_text('INSTALLER')
|
||||
except (OSError, ValueError, NoneMetadataError):
|
||||
return "" # Fail silently if the installer file cannot be read.
|
||||
return '' # Fail silently if the installer file cannot be read.
|
||||
for line in installer_text.splitlines():
|
||||
cleaned_line = line.strip()
|
||||
if cleaned_line:
|
||||
return cleaned_line
|
||||
return ""
|
||||
return ''
|
||||
|
||||
@property
|
||||
def editable(self) -> bool:
|
||||
|
|
@ -350,16 +349,16 @@ class BaseDistribution(Protocol):
|
|||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def metadata_version(self) -> Optional[str]:
|
||||
def metadata_version(self) -> str | None:
|
||||
"""Value of "Metadata-Version:" in distribution metadata, if available."""
|
||||
return self.metadata.get("Metadata-Version")
|
||||
return self.metadata.get('Metadata-Version')
|
||||
|
||||
@property
|
||||
def raw_name(self) -> str:
|
||||
"""Value of "Name:" in distribution metadata."""
|
||||
# The metadata should NEVER be missing the Name: key, but if it somehow
|
||||
# does, fall back to the known canonical name.
|
||||
return self.metadata.get("Name", self.canonical_name)
|
||||
return self.metadata.get('Name', self.canonical_name)
|
||||
|
||||
@property
|
||||
def requires_python(self) -> SpecifierSet:
|
||||
|
|
@ -368,14 +367,14 @@ class BaseDistribution(Protocol):
|
|||
If the key does not exist or contains an invalid value, an empty
|
||||
SpecifierSet should be returned.
|
||||
"""
|
||||
value = self.metadata.get("Requires-Python")
|
||||
value = self.metadata.get('Requires-Python')
|
||||
if value is None:
|
||||
return SpecifierSet()
|
||||
try:
|
||||
# Convert to str to satisfy the type checker; this can be a Header object.
|
||||
spec = SpecifierSet(str(value))
|
||||
except InvalidSpecifier as e:
|
||||
message = "Package %r has an invalid Requires-Python: %s"
|
||||
message = 'Package %r has an invalid Requires-Python: %s'
|
||||
logger.warning(message, self.raw_name, e)
|
||||
return SpecifierSet()
|
||||
return spec
|
||||
|
|
@ -396,17 +395,17 @@ class BaseDistribution(Protocol):
|
|||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _iter_declared_entries_from_record(self) -> Optional[Iterator[str]]:
|
||||
def _iter_declared_entries_from_record(self) -> Iterator[str] | None:
|
||||
try:
|
||||
text = self.read_text("RECORD")
|
||||
text = self.read_text('RECORD')
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
# This extra Path-str cast normalizes entries.
|
||||
return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines()))
|
||||
|
||||
def _iter_declared_entries_from_legacy(self) -> Optional[Iterator[str]]:
|
||||
def _iter_declared_entries_from_legacy(self) -> Iterator[str] | None:
|
||||
try:
|
||||
text = self.read_text("installed-files.txt")
|
||||
text = self.read_text('installed-files.txt')
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
paths = (p for p in text.splitlines(keepends=False) if p)
|
||||
|
|
@ -425,7 +424,7 @@ class BaseDistribution(Protocol):
|
|||
for p in paths
|
||||
)
|
||||
|
||||
def iter_declared_entries(self) -> Optional[Iterator[str]]:
|
||||
def iter_declared_entries(self) -> Iterator[str] | None:
|
||||
"""Iterate through file entires declared in this distribution.
|
||||
|
||||
For modern .dist-info distributions, this is the files listed in the
|
||||
|
|
@ -437,8 +436,8 @@ class BaseDistribution(Protocol):
|
|||
contains neither ``RECORD`` nor ``installed-files.txt``.
|
||||
"""
|
||||
return (
|
||||
self._iter_declared_entries_from_record()
|
||||
or self._iter_declared_entries_from_legacy()
|
||||
self._iter_declared_entries_from_record() or
|
||||
self._iter_declared_entries_from_legacy()
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -446,14 +445,14 @@ class BaseEnvironment:
|
|||
"""An environment containing distributions to introspect."""
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> "BaseEnvironment":
|
||||
def default(cls) -> BaseEnvironment:
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def from_paths(cls, paths: Optional[List[str]]) -> "BaseEnvironment":
|
||||
def from_paths(cls, paths: list[str] | None) -> BaseEnvironment:
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_distribution(self, name: str) -> Optional["BaseDistribution"]:
|
||||
def get_distribution(self, name: str) -> BaseDistribution | None:
|
||||
"""Given a requirement name, return the installed distributions.
|
||||
|
||||
The name may not be normalized. The implementation must canonicalize
|
||||
|
|
@ -461,7 +460,7 @@ class BaseEnvironment:
|
|||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _iter_distributions(self) -> Iterator["BaseDistribution"]:
|
||||
def _iter_distributions(self) -> Iterator[BaseDistribution]:
|
||||
"""Iterate through installed distributions.
|
||||
|
||||
This function should be implemented by subclass, but never called
|
||||
|
|
@ -470,7 +469,7 @@ class BaseEnvironment:
|
|||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iter_distributions(self) -> Iterator["BaseDistribution"]:
|
||||
def iter_distributions(self) -> Iterator[BaseDistribution]:
|
||||
"""Iterate through installed distributions."""
|
||||
for dist in self._iter_distributions():
|
||||
# Make sure the distribution actually comes from a valid Python
|
||||
|
|
@ -478,13 +477,13 @@ class BaseEnvironment:
|
|||
# e.g. ``~atplotlib.dist-info`` if cleanup was interrupted. The
|
||||
# valid project name pattern is taken from PEP 508.
|
||||
project_name_valid = re.match(
|
||||
r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$",
|
||||
r'^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$',
|
||||
dist.canonical_name,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
if not project_name_valid:
|
||||
logger.warning(
|
||||
"Ignoring invalid distribution %s (%s)",
|
||||
'Ignoring invalid distribution %s (%s)',
|
||||
dist.canonical_name,
|
||||
dist.location,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,28 +1,37 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import email.message
|
||||
import email.parser
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import zipfile
|
||||
from typing import Collection, Iterable, Iterator, List, Mapping, NamedTuple, Optional
|
||||
from typing import Collection
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.exceptions import InvalidWheel
|
||||
from pip._internal.exceptions import NoneMetadataError
|
||||
from pip._internal.exceptions import UnsupportedWheel
|
||||
from pip._internal.utils.misc import display_path
|
||||
from pip._internal.utils.wheel import parse_wheel
|
||||
from pip._internal.utils.wheel import read_wheel_metadata_file
|
||||
from pip._vendor import pkg_resources
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
|
||||
from pip._internal.exceptions import InvalidWheel, NoneMetadataError, UnsupportedWheel
|
||||
from pip._internal.utils.misc import display_path
|
||||
from pip._internal.utils.wheel import parse_wheel, read_wheel_metadata_file
|
||||
|
||||
from .base import (
|
||||
BaseDistribution,
|
||||
BaseEntryPoint,
|
||||
BaseEnvironment,
|
||||
DistributionVersion,
|
||||
InfoPath,
|
||||
Wheel,
|
||||
)
|
||||
from .base import BaseDistribution
|
||||
from .base import BaseEntryPoint
|
||||
from .base import BaseEnvironment
|
||||
from .base import DistributionVersion
|
||||
from .base import InfoPath
|
||||
from .base import Wheel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -52,7 +61,7 @@ class WheelMetadata:
|
|||
except UnicodeDecodeError as e:
|
||||
# Augment the default error with the origin of the file.
|
||||
raise UnsupportedWheel(
|
||||
f"Error decoding metadata for {self._wheel_name}: {e} in {name} file"
|
||||
f'Error decoding metadata for {self._wheel_name}: {e} in {name} file',
|
||||
)
|
||||
|
||||
def get_metadata_lines(self, name: str) -> Iterable[str]:
|
||||
|
|
@ -61,7 +70,7 @@ class WheelMetadata:
|
|||
def metadata_isdir(self, name: str) -> bool:
|
||||
return False
|
||||
|
||||
def metadata_listdir(self, name: str) -> List[str]:
|
||||
def metadata_listdir(self, name: str) -> list[str]:
|
||||
return []
|
||||
|
||||
def run_script(self, script_name: str, namespace: str) -> None:
|
||||
|
|
@ -73,7 +82,7 @@ class Distribution(BaseDistribution):
|
|||
self._dist = dist
|
||||
|
||||
@classmethod
|
||||
def from_directory(cls, directory: str) -> "Distribution":
|
||||
def from_directory(cls, directory: str) -> Distribution:
|
||||
dist_dir = directory.rstrip(os.sep)
|
||||
|
||||
# Build a PathMetadata object, from path to metadata. :wink:
|
||||
|
|
@ -81,19 +90,19 @@ class Distribution(BaseDistribution):
|
|||
metadata = pkg_resources.PathMetadata(base_dir, dist_dir)
|
||||
|
||||
# Determine the correct Distribution object type.
|
||||
if dist_dir.endswith(".egg-info"):
|
||||
if dist_dir.endswith('.egg-info'):
|
||||
dist_cls = pkg_resources.Distribution
|
||||
dist_name = os.path.splitext(dist_dir_name)[0]
|
||||
else:
|
||||
assert dist_dir.endswith(".dist-info")
|
||||
assert dist_dir.endswith('.dist-info')
|
||||
dist_cls = pkg_resources.DistInfoDistribution
|
||||
dist_name = os.path.splitext(dist_dir_name)[0].split("-")[0]
|
||||
dist_name = os.path.splitext(dist_dir_name)[0].split('-')[0]
|
||||
|
||||
dist = dist_cls(base_dir, project_name=dist_name, metadata=metadata)
|
||||
return cls(dist)
|
||||
|
||||
@classmethod
|
||||
def from_wheel(cls, wheel: Wheel, name: str) -> "Distribution":
|
||||
def from_wheel(cls, wheel: Wheel, name: str) -> Distribution:
|
||||
"""Load the distribution from a given wheel.
|
||||
|
||||
:raises InvalidWheel: Whenever loading of the wheel causes a
|
||||
|
|
@ -105,14 +114,14 @@ class Distribution(BaseDistribution):
|
|||
with wheel.as_zipfile() as zf:
|
||||
info_dir, _ = parse_wheel(zf, name)
|
||||
metadata_text = {
|
||||
path.split("/", 1)[-1]: read_wheel_metadata_file(zf, path)
|
||||
path.split('/', 1)[-1]: read_wheel_metadata_file(zf, path)
|
||||
for path in zf.namelist()
|
||||
if path.startswith(f"{info_dir}/")
|
||||
if path.startswith(f'{info_dir}/')
|
||||
}
|
||||
except zipfile.BadZipFile as e:
|
||||
raise InvalidWheel(wheel.location, name) from e
|
||||
except UnsupportedWheel as e:
|
||||
raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
|
||||
raise UnsupportedWheel(f'{name} has an invalid wheel, {e}')
|
||||
dist = pkg_resources.DistInfoDistribution(
|
||||
location=wheel.location,
|
||||
metadata=WheelMetadata(metadata_text, wheel.location),
|
||||
|
|
@ -121,11 +130,11 @@ class Distribution(BaseDistribution):
|
|||
return cls(dist)
|
||||
|
||||
@property
|
||||
def location(self) -> Optional[str]:
|
||||
def location(self) -> str | None:
|
||||
return self._dist.location
|
||||
|
||||
@property
|
||||
def info_location(self) -> Optional[str]:
|
||||
def info_location(self) -> str | None:
|
||||
return self._dist.egg_info
|
||||
|
||||
@property
|
||||
|
|
@ -170,7 +179,7 @@ class Distribution(BaseDistribution):
|
|||
def iter_entry_points(self) -> Iterable[BaseEntryPoint]:
|
||||
for group, entries in self._dist.get_entry_map().items():
|
||||
for name, entry_point in entries.items():
|
||||
name, _, value = str(entry_point).partition("=")
|
||||
name, _, value = str(entry_point).partition('=')
|
||||
yield EntryPoint(name=name.strip(), value=value.strip(), group=group)
|
||||
|
||||
@property
|
||||
|
|
@ -180,9 +189,9 @@ class Distribution(BaseDistribution):
|
|||
True but `get_metadata()` returns None.
|
||||
"""
|
||||
if isinstance(self._dist, pkg_resources.DistInfoDistribution):
|
||||
metadata_name = "METADATA"
|
||||
metadata_name = 'METADATA'
|
||||
else:
|
||||
metadata_name = "PKG-INFO"
|
||||
metadata_name = 'PKG-INFO'
|
||||
try:
|
||||
metadata = self.read_text(metadata_name)
|
||||
except FileNotFoundError:
|
||||
|
|
@ -190,8 +199,8 @@ class Distribution(BaseDistribution):
|
|||
displaying_path = display_path(self.location)
|
||||
else:
|
||||
displaying_path = repr(self.location)
|
||||
logger.warning("No metadata found in %s", displaying_path)
|
||||
metadata = ""
|
||||
logger.warning('No metadata found in %s', displaying_path)
|
||||
metadata = ''
|
||||
feed_parser = email.parser.FeedParser()
|
||||
feed_parser.feed(metadata)
|
||||
return feed_parser.close()
|
||||
|
|
@ -214,10 +223,10 @@ class Environment(BaseEnvironment):
|
|||
return cls(pkg_resources.working_set)
|
||||
|
||||
@classmethod
|
||||
def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
|
||||
def from_paths(cls, paths: list[str] | None) -> BaseEnvironment:
|
||||
return cls(pkg_resources.WorkingSet(paths))
|
||||
|
||||
def _search_distribution(self, name: str) -> Optional[BaseDistribution]:
|
||||
def _search_distribution(self, name: str) -> BaseDistribution | None:
|
||||
"""Find a distribution matching the ``name`` in the environment.
|
||||
|
||||
This searches from *all* distributions available in the environment, to
|
||||
|
|
@ -229,7 +238,7 @@ class Environment(BaseEnvironment):
|
|||
return dist
|
||||
return None
|
||||
|
||||
def get_distribution(self, name: str) -> Optional[BaseDistribution]:
|
||||
def get_distribution(self, name: str) -> BaseDistribution | None:
|
||||
# Search the distribution by looking through the working set.
|
||||
dist = self._search_distribution(name)
|
||||
if dist:
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
"""A package that contains models that represent entities.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
from pip._vendor.packaging.version import parse as parse_version
|
||||
from __future__ import annotations
|
||||
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.utils.models import KeyBasedCompareMixin
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
|
||||
|
||||
class InstallationCandidate(KeyBasedCompareMixin):
|
||||
"""Represents a potential "candidate" for installation."""
|
||||
|
||||
__slots__ = ["name", "version", "link"]
|
||||
__slots__ = ['name', 'version', 'link']
|
||||
|
||||
def __init__(self, name: str, version: str, link: Link) -> None:
|
||||
self.name = name
|
||||
|
|
@ -20,14 +21,14 @@ class InstallationCandidate(KeyBasedCompareMixin):
|
|||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<InstallationCandidate({!r}, {!r}, {!r})>".format(
|
||||
return '<InstallationCandidate({!r}, {!r}, {!r})>'.format(
|
||||
self.name,
|
||||
self.version,
|
||||
self.link,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "{!r} candidate (version {} at {})".format(
|
||||
return '{!r} candidate (version {} at {})'.format(
|
||||
self.name,
|
||||
self.version,
|
||||
self.link,
|
||||
|
|
|
|||
|
|
@ -1,21 +1,29 @@
|
|||
""" PEP 610 """
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import urllib.parse
|
||||
from typing import Any, Dict, Iterable, Optional, Type, TypeVar, Union
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import Optional
|
||||
from typing import Type
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
__all__ = [
|
||||
"DirectUrl",
|
||||
"DirectUrlValidationError",
|
||||
"DirInfo",
|
||||
"ArchiveInfo",
|
||||
"VcsInfo",
|
||||
'DirectUrl',
|
||||
'DirectUrlValidationError',
|
||||
'DirInfo',
|
||||
'ArchiveInfo',
|
||||
'VcsInfo',
|
||||
]
|
||||
|
||||
T = TypeVar("T")
|
||||
T = TypeVar('T')
|
||||
|
||||
DIRECT_URL_METADATA_NAME = "direct_url.json"
|
||||
ENV_VAR_RE = re.compile(r"^\$\{[A-Za-z0-9-_]+\}(:\$\{[A-Za-z0-9-_]+\})?$")
|
||||
DIRECT_URL_METADATA_NAME = 'direct_url.json'
|
||||
ENV_VAR_RE = re.compile(r'^\$\{[A-Za-z0-9-_]+\}(:\$\{[A-Za-z0-9-_]+\})?$')
|
||||
|
||||
|
||||
class DirectUrlValidationError(Exception):
|
||||
|
|
@ -23,59 +31,59 @@ class DirectUrlValidationError(Exception):
|
|||
|
||||
|
||||
def _get(
|
||||
d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None
|
||||
) -> Optional[T]:
|
||||
d: dict[str, Any], expected_type: type[T], key: str, default: T | None = None,
|
||||
) -> T | None:
|
||||
"""Get value from dictionary and verify expected type."""
|
||||
if key not in d:
|
||||
return default
|
||||
value = d[key]
|
||||
if not isinstance(value, expected_type):
|
||||
raise DirectUrlValidationError(
|
||||
"{!r} has unexpected type for {} (expected {})".format(
|
||||
value, key, expected_type
|
||||
)
|
||||
'{!r} has unexpected type for {} (expected {})'.format(
|
||||
value, key, expected_type,
|
||||
),
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def _get_required(
|
||||
d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None
|
||||
d: dict[str, Any], expected_type: type[T], key: str, default: T | None = None,
|
||||
) -> T:
|
||||
value = _get(d, expected_type, key, default)
|
||||
if value is None:
|
||||
raise DirectUrlValidationError(f"{key} must have a value")
|
||||
raise DirectUrlValidationError(f'{key} must have a value')
|
||||
return value
|
||||
|
||||
|
||||
def _exactly_one_of(infos: Iterable[Optional["InfoType"]]) -> "InfoType":
|
||||
def _exactly_one_of(infos: Iterable[InfoType | None]) -> InfoType:
|
||||
infos = [info for info in infos if info is not None]
|
||||
if not infos:
|
||||
raise DirectUrlValidationError(
|
||||
"missing one of archive_info, dir_info, vcs_info"
|
||||
'missing one of archive_info, dir_info, vcs_info',
|
||||
)
|
||||
if len(infos) > 1:
|
||||
raise DirectUrlValidationError(
|
||||
"more than one of archive_info, dir_info, vcs_info"
|
||||
'more than one of archive_info, dir_info, vcs_info',
|
||||
)
|
||||
assert infos[0] is not None
|
||||
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}
|
||||
|
||||
|
||||
class VcsInfo:
|
||||
name = "vcs_info"
|
||||
name = 'vcs_info'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vcs: str,
|
||||
commit_id: str,
|
||||
requested_revision: Optional[str] = None,
|
||||
resolved_revision: Optional[str] = None,
|
||||
resolved_revision_type: Optional[str] = None,
|
||||
requested_revision: str | None = None,
|
||||
resolved_revision: str | None = None,
|
||||
resolved_revision_type: str | None = None,
|
||||
) -> None:
|
||||
self.vcs = vcs
|
||||
self.requested_revision = requested_revision
|
||||
|
|
@ -84,18 +92,18 @@ class VcsInfo:
|
|||
self.resolved_revision_type = resolved_revision_type
|
||||
|
||||
@classmethod
|
||||
def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["VcsInfo"]:
|
||||
def _from_dict(cls, d: dict[str, Any] | None) -> VcsInfo | None:
|
||||
if d is None:
|
||||
return None
|
||||
return cls(
|
||||
vcs=_get_required(d, str, "vcs"),
|
||||
commit_id=_get_required(d, str, "commit_id"),
|
||||
requested_revision=_get(d, str, "requested_revision"),
|
||||
resolved_revision=_get(d, str, "resolved_revision"),
|
||||
resolved_revision_type=_get(d, str, "resolved_revision_type"),
|
||||
vcs=_get_required(d, str, 'vcs'),
|
||||
commit_id=_get_required(d, str, 'commit_id'),
|
||||
requested_revision=_get(d, str, 'requested_revision'),
|
||||
resolved_revision=_get(d, str, 'resolved_revision'),
|
||||
resolved_revision_type=_get(d, str, 'resolved_revision_type'),
|
||||
)
|
||||
|
||||
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,
|
||||
|
|
@ -106,26 +114,26 @@ class VcsInfo:
|
|||
|
||||
|
||||
class ArchiveInfo:
|
||||
name = "archive_info"
|
||||
name = 'archive_info'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hash: Optional[str] = None,
|
||||
hash: str | None = None,
|
||||
) -> None:
|
||||
self.hash = hash
|
||||
|
||||
@classmethod
|
||||
def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["ArchiveInfo"]:
|
||||
def _from_dict(cls, d: dict[str, Any] | None) -> ArchiveInfo | None:
|
||||
if d is None:
|
||||
return None
|
||||
return cls(hash=_get(d, str, "hash"))
|
||||
return cls(hash=_get(d, str, 'hash'))
|
||||
|
||||
def _to_dict(self) -> Dict[str, Any]:
|
||||
def _to_dict(self) -> dict[str, Any]:
|
||||
return _filter_none(hash=self.hash)
|
||||
|
||||
|
||||
class DirInfo:
|
||||
name = "dir_info"
|
||||
name = 'dir_info'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -134,12 +142,12 @@ class DirInfo:
|
|||
self.editable = editable
|
||||
|
||||
@classmethod
|
||||
def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["DirInfo"]:
|
||||
def _from_dict(cls, d: dict[str, Any] | None) -> DirInfo | None:
|
||||
if d is None:
|
||||
return None
|
||||
return cls(editable=_get_required(d, bool, "editable", default=False))
|
||||
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)
|
||||
|
||||
|
||||
|
|
@ -151,20 +159,20 @@ class DirectUrl:
|
|||
self,
|
||||
url: str,
|
||||
info: InfoType,
|
||||
subdirectory: Optional[str] = None,
|
||||
subdirectory: str | None = None,
|
||||
) -> None:
|
||||
self.url = url
|
||||
self.info = info
|
||||
self.subdirectory = subdirectory
|
||||
|
||||
def _remove_auth_from_netloc(self, netloc: str) -> str:
|
||||
if "@" not in netloc:
|
||||
if '@' not in netloc:
|
||||
return netloc
|
||||
user_pass, netloc_no_user_pass = netloc.split("@", 1)
|
||||
user_pass, netloc_no_user_pass = netloc.split('@', 1)
|
||||
if (
|
||||
isinstance(self.info, VcsInfo)
|
||||
and self.info.vcs == "git"
|
||||
and user_pass == "git"
|
||||
isinstance(self.info, VcsInfo) and
|
||||
self.info.vcs == 'git' and
|
||||
user_pass == 'git'
|
||||
):
|
||||
return netloc
|
||||
if ENV_VAR_RE.match(user_pass):
|
||||
|
|
@ -180,7 +188,7 @@ class DirectUrl:
|
|||
purl = urllib.parse.urlsplit(self.url)
|
||||
netloc = self._remove_auth_from_netloc(purl.netloc)
|
||||
surl = urllib.parse.urlunsplit(
|
||||
(purl.scheme, netloc, purl.path, purl.query, purl.fragment)
|
||||
(purl.scheme, netloc, purl.path, purl.query, purl.fragment),
|
||||
)
|
||||
return surl
|
||||
|
||||
|
|
@ -188,20 +196,20 @@ 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"),
|
||||
url=_get_required(d, str, 'url'),
|
||||
subdirectory=_get(d, str, 'subdirectory'),
|
||||
info=_exactly_one_of(
|
||||
[
|
||||
ArchiveInfo._from_dict(_get(d, dict, "archive_info")),
|
||||
DirInfo._from_dict(_get(d, dict, "dir_info")),
|
||||
VcsInfo._from_dict(_get(d, dict, "vcs_info")),
|
||||
]
|
||||
ArchiveInfo._from_dict(_get(d, dict, 'archive_info')),
|
||||
DirInfo._from_dict(_get(d, dict, 'dir_info')),
|
||||
VcsInfo._from_dict(_get(d, dict, 'vcs_info')),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
res = _filter_none(
|
||||
url=self.redacted_url,
|
||||
subdirectory=self.subdirectory,
|
||||
|
|
@ -210,7 +218,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:
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
from typing import FrozenSet, Optional, Set
|
||||
from __future__ import annotations
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from typing import FrozenSet
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
|
||||
class FormatControl:
|
||||
"""Helper for managing formats from which a package can be installed."""
|
||||
|
||||
__slots__ = ["no_binary", "only_binary"]
|
||||
__slots__ = ['no_binary', 'only_binary']
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
no_binary: Optional[Set[str]] = None,
|
||||
only_binary: Optional[Set[str]] = None,
|
||||
no_binary: set[str] | None = None,
|
||||
only_binary: set[str] | None = None,
|
||||
) -> None:
|
||||
if no_binary is None:
|
||||
no_binary = set()
|
||||
|
|
@ -33,48 +36,48 @@ class FormatControl:
|
|||
return all(getattr(self, k) == getattr(other, k) for k in self.__slots__)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{}({}, {})".format(
|
||||
self.__class__.__name__, self.no_binary, self.only_binary
|
||||
return '{}({}, {})'.format(
|
||||
self.__class__.__name__, self.no_binary, self.only_binary,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> None:
|
||||
if value.startswith("-"):
|
||||
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."
|
||||
'--no-binary / --only-binary option requires 1 argument.',
|
||||
)
|
||||
new = value.split(",")
|
||||
while ":all:" in new:
|
||||
new = value.split(',')
|
||||
while ':all:' in new:
|
||||
other.clear()
|
||||
target.clear()
|
||||
target.add(":all:")
|
||||
del new[: new.index(":all:") + 1]
|
||||
target.add(':all:')
|
||||
del new[: new.index(':all:') + 1]
|
||||
# Without a none, we want to discard everything as :all: covers it
|
||||
if ":none:" not in new:
|
||||
if ':none:' not in new:
|
||||
return
|
||||
for name in new:
|
||||
if name == ":none:":
|
||||
if name == ':none:':
|
||||
target.clear()
|
||||
continue
|
||||
name = canonicalize_name(name)
|
||||
other.discard(name)
|
||||
target.add(name)
|
||||
|
||||
def get_allowed_formats(self, canonical_name: str) -> FrozenSet[str]:
|
||||
result = {"binary", "source"}
|
||||
def get_allowed_formats(self, canonical_name: str) -> frozenset[str]:
|
||||
result = {'binary', 'source'}
|
||||
if canonical_name in self.only_binary:
|
||||
result.discard("source")
|
||||
result.discard('source')
|
||||
elif canonical_name in self.no_binary:
|
||||
result.discard("binary")
|
||||
elif ":all:" in self.only_binary:
|
||||
result.discard("source")
|
||||
elif ":all:" in self.no_binary:
|
||||
result.discard("binary")
|
||||
result.discard('binary')
|
||||
elif ':all:' in self.only_binary:
|
||||
result.discard('source')
|
||||
elif ':all:' in self.no_binary:
|
||||
result.discard('binary')
|
||||
return frozenset(result)
|
||||
|
||||
def disallow_binaries(self) -> None:
|
||||
self.handle_mutual_excludes(
|
||||
":all:",
|
||||
':all:',
|
||||
self.no_binary,
|
||||
self.only_binary,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import urllib.parse
|
||||
|
||||
|
||||
class PackageIndex:
|
||||
"""Represents a Package Index and provides easier access to endpoints"""
|
||||
|
||||
__slots__ = ["url", "netloc", "simple_url", "pypi_url", "file_storage_domain"]
|
||||
__slots__ = ['url', 'netloc', 'simple_url', 'pypi_url', 'file_storage_domain']
|
||||
|
||||
def __init__(self, url: str, file_storage_domain: str) -> None:
|
||||
super().__init__()
|
||||
self.url = url
|
||||
self.netloc = urllib.parse.urlsplit(url).netloc
|
||||
self.simple_url = self._url_for_path("simple")
|
||||
self.pypi_url = self._url_for_path("pypi")
|
||||
self.simple_url = self._url_for_path('simple')
|
||||
self.pypi_url = self._url_for_path('pypi')
|
||||
|
||||
# This is part of a temporary hack used to block installs of PyPI
|
||||
# packages which depend on external urls only necessary until PyPI can
|
||||
|
|
@ -22,7 +24,7 @@ class PackageIndex:
|
|||
return urllib.parse.urljoin(self.url, path)
|
||||
|
||||
|
||||
PyPI = PackageIndex("https://pypi.org/", file_storage_domain="files.pythonhosted.org")
|
||||
PyPI = PackageIndex('https://pypi.org/', file_storage_domain='files.pythonhosted.org')
|
||||
TestPyPI = PackageIndex(
|
||||
"https://test.pypi.org/", file_storage_domain="test-files.pythonhosted.org"
|
||||
'https://test.pypi.org/', file_storage_domain='test-files.pythonhosted.org',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,20 +1,27 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
import urllib.parse
|
||||
from typing import TYPE_CHECKING, Dict, List, NamedTuple, Optional, Tuple, Union
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from pip._internal.utils.filetypes import WHEEL_EXTENSION
|
||||
from pip._internal.utils.hashes import Hashes
|
||||
from pip._internal.utils.misc import (
|
||||
redact_auth_from_url,
|
||||
split_auth_from_netloc,
|
||||
splitext,
|
||||
)
|
||||
from pip._internal.utils.misc import redact_auth_from_url
|
||||
from pip._internal.utils.misc import split_auth_from_netloc
|
||||
from pip._internal.utils.misc import splitext
|
||||
from pip._internal.utils.models import KeyBasedCompareMixin
|
||||
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
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pip._internal.index.collector import HTMLPage
|
||||
|
|
@ -22,27 +29,27 @@ if TYPE_CHECKING:
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
_SUPPORTED_HASHES = ("sha1", "sha224", "sha384", "sha256", "sha512", "md5")
|
||||
_SUPPORTED_HASHES = ('sha1', 'sha224', 'sha384', 'sha256', 'sha512', 'md5')
|
||||
|
||||
|
||||
class Link(KeyBasedCompareMixin):
|
||||
"""Represents a parsed link from a Package Index's simple URL"""
|
||||
|
||||
__slots__ = [
|
||||
"_parsed_url",
|
||||
"_url",
|
||||
"comes_from",
|
||||
"requires_python",
|
||||
"yanked_reason",
|
||||
"cache_link_parsing",
|
||||
'_parsed_url',
|
||||
'_url',
|
||||
'comes_from',
|
||||
'requires_python',
|
||||
'yanked_reason',
|
||||
'cache_link_parsing',
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url: str,
|
||||
comes_from: Optional[Union[str, "HTMLPage"]] = None,
|
||||
requires_python: Optional[str] = None,
|
||||
yanked_reason: Optional[str] = None,
|
||||
comes_from: str | HTMLPage | None = None,
|
||||
requires_python: str | None = None,
|
||||
yanked_reason: str | None = None,
|
||||
cache_link_parsing: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -67,7 +74,7 @@ class Link(KeyBasedCompareMixin):
|
|||
"""
|
||||
|
||||
# url can be a UNC windows share
|
||||
if url.startswith("\\\\"):
|
||||
if url.startswith('\\\\'):
|
||||
url = path_to_url(url)
|
||||
|
||||
self._parsed_url = urllib.parse.urlsplit(url)
|
||||
|
|
@ -85,18 +92,18 @@ class Link(KeyBasedCompareMixin):
|
|||
|
||||
def __str__(self) -> str:
|
||||
if self.requires_python:
|
||||
rp = f" (requires-python:{self.requires_python})"
|
||||
rp = f' (requires-python:{self.requires_python})'
|
||||
else:
|
||||
rp = ""
|
||||
rp = ''
|
||||
if self.comes_from:
|
||||
return "{} (from {}){}".format(
|
||||
redact_auth_from_url(self._url), self.comes_from, rp
|
||||
return '{} (from {}){}'.format(
|
||||
redact_auth_from_url(self._url), self.comes_from, rp,
|
||||
)
|
||||
else:
|
||||
return redact_auth_from_url(str(self._url))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Link {self}>"
|
||||
return f'<Link {self}>'
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
|
|
@ -104,7 +111,7 @@ class Link(KeyBasedCompareMixin):
|
|||
|
||||
@property
|
||||
def filename(self) -> str:
|
||||
path = self.path.rstrip("/")
|
||||
path = self.path.rstrip('/')
|
||||
name = posixpath.basename(path)
|
||||
if not name:
|
||||
# Make sure we don't leak auth information if the netloc
|
||||
|
|
@ -113,7 +120,7 @@ class Link(KeyBasedCompareMixin):
|
|||
return netloc
|
||||
|
||||
name = urllib.parse.unquote(name)
|
||||
assert name, f"URL {self._url!r} produced no filename"
|
||||
assert name, f'URL {self._url!r} produced no filename'
|
||||
return name
|
||||
|
||||
@property
|
||||
|
|
@ -135,8 +142,8 @@ class Link(KeyBasedCompareMixin):
|
|||
def path(self) -> str:
|
||||
return urllib.parse.unquote(self._parsed_url.path)
|
||||
|
||||
def splitext(self) -> Tuple[str, str]:
|
||||
return splitext(posixpath.basename(self.path.rstrip("/")))
|
||||
def splitext(self) -> tuple[str, str]:
|
||||
return splitext(posixpath.basename(self.path.rstrip('/')))
|
||||
|
||||
@property
|
||||
def ext(self) -> str:
|
||||
|
|
@ -145,39 +152,39 @@ class Link(KeyBasedCompareMixin):
|
|||
@property
|
||||
def url_without_fragment(self) -> str:
|
||||
scheme, netloc, path, query, fragment = self._parsed_url
|
||||
return urllib.parse.urlunsplit((scheme, netloc, path, query, ""))
|
||||
return urllib.parse.urlunsplit((scheme, netloc, path, query, ''))
|
||||
|
||||
_egg_fragment_re = re.compile(r"[#&]egg=([^&]*)")
|
||||
_egg_fragment_re = re.compile(r'[#&]egg=([^&]*)')
|
||||
|
||||
@property
|
||||
def egg_fragment(self) -> Optional[str]:
|
||||
def egg_fragment(self) -> str | None:
|
||||
match = self._egg_fragment_re.search(self._url)
|
||||
if not match:
|
||||
return None
|
||||
return match.group(1)
|
||||
|
||||
_subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")
|
||||
_subdirectory_fragment_re = re.compile(r'[#&]subdirectory=([^&]*)')
|
||||
|
||||
@property
|
||||
def subdirectory_fragment(self) -> Optional[str]:
|
||||
def subdirectory_fragment(self) -> str | None:
|
||||
match = self._subdirectory_fragment_re.search(self._url)
|
||||
if not match:
|
||||
return None
|
||||
return match.group(1)
|
||||
|
||||
_hash_re = re.compile(
|
||||
r"({choices})=([a-f0-9]+)".format(choices="|".join(_SUPPORTED_HASHES))
|
||||
r'({choices})=([a-f0-9]+)'.format(choices='|'.join(_SUPPORTED_HASHES)),
|
||||
)
|
||||
|
||||
@property
|
||||
def hash(self) -> Optional[str]:
|
||||
def hash(self) -> str | None:
|
||||
match = self._hash_re.search(self._url)
|
||||
if match:
|
||||
return match.group(2)
|
||||
return None
|
||||
|
||||
@property
|
||||
def hash_name(self) -> Optional[str]:
|
||||
def hash_name(self) -> str | None:
|
||||
match = self._hash_re.search(self._url)
|
||||
if match:
|
||||
return match.group(1)
|
||||
|
|
@ -185,11 +192,11 @@ class Link(KeyBasedCompareMixin):
|
|||
|
||||
@property
|
||||
def show_url(self) -> str:
|
||||
return posixpath.basename(self._url.split("#", 1)[0].split("?", 1)[0])
|
||||
return posixpath.basename(self._url.split('#', 1)[0].split('?', 1)[0])
|
||||
|
||||
@property
|
||||
def is_file(self) -> bool:
|
||||
return self.scheme == "file"
|
||||
return self.scheme == 'file'
|
||||
|
||||
def is_existing_dir(self) -> bool:
|
||||
return self.is_file and os.path.isdir(self.file_path)
|
||||
|
|
@ -212,7 +219,7 @@ class Link(KeyBasedCompareMixin):
|
|||
def has_hash(self) -> bool:
|
||||
return self.hash_name is not None
|
||||
|
||||
def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
|
||||
def is_hash_allowed(self, hashes: Hashes | None) -> bool:
|
||||
"""
|
||||
Return True if the link has a hash and it is allowed.
|
||||
"""
|
||||
|
|
@ -252,31 +259,31 @@ 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:
|
||||
parsed = link._parsed_url
|
||||
netloc = parsed.netloc.rsplit("@", 1)[-1]
|
||||
netloc = parsed.netloc.rsplit('@', 1)[-1]
|
||||
# According to RFC 8089, an empty host in file: means localhost.
|
||||
if parsed.scheme == "file" and not netloc:
|
||||
netloc = "localhost"
|
||||
if parsed.scheme == 'file' and not netloc:
|
||||
netloc = 'localhost'
|
||||
fragment = urllib.parse.parse_qs(parsed.fragment)
|
||||
if "egg" in fragment:
|
||||
logger.debug("Ignoring egg= fragment in %s", link)
|
||||
if 'egg' in fragment:
|
||||
logger.debug('Ignoring egg= fragment in %s', link)
|
||||
try:
|
||||
# If there are multiple subdirectory values, use the first one.
|
||||
# This matches the behavior of Link.subdirectory_fragment.
|
||||
subdirectory = fragment["subdirectory"][0]
|
||||
subdirectory = fragment['subdirectory'][0]
|
||||
except (IndexError, KeyError):
|
||||
subdirectory = ""
|
||||
subdirectory = ''
|
||||
# If there are multiple hash values under the same algorithm, use the
|
||||
# first one. This matches the behavior of Link.hash_value.
|
||||
hashes = {k: fragment[k][0] for k in _SUPPORTED_HASHES if k in fragment}
|
||||
return _CleanResult(
|
||||
parsed=parsed._replace(netloc=netloc, query="", fragment=""),
|
||||
parsed=parsed._replace(netloc=netloc, query='', fragment=''),
|
||||
query=urllib.parse.parse_qs(parsed.query),
|
||||
subdirectory=subdirectory,
|
||||
hashes=hashes,
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ For types associated with installation schemes.
|
|||
For a general overview of available schemes and their context, see
|
||||
https://docs.python.org/3/install/index.html#alternate-installation.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
SCHEME_KEYS = ["platlib", "purelib", "headers", "scripts", "data"]
|
||||
SCHEME_KEYS = ['platlib', 'purelib', 'headers', 'scripts', 'data']
|
||||
|
||||
|
||||
class Scheme:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -5,11 +7,11 @@ import posixpath
|
|||
import urllib.parse
|
||||
from typing import List
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
from pip._internal.models.index import PyPI
|
||||
from pip._internal.utils.compat import has_tls
|
||||
from pip._internal.utils.misc import normalize_path, redact_auth_from_url
|
||||
from pip._internal.utils.misc import normalize_path
|
||||
from pip._internal.utils.misc import redact_auth_from_url
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -20,14 +22,14 @@ class SearchScope:
|
|||
Encapsulates the locations that pip is configured to search.
|
||||
"""
|
||||
|
||||
__slots__ = ["find_links", "index_urls"]
|
||||
__slots__ = ['find_links', 'index_urls']
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
find_links: List[str],
|
||||
index_urls: List[str],
|
||||
) -> "SearchScope":
|
||||
find_links: list[str],
|
||||
index_urls: list[str],
|
||||
) -> SearchScope:
|
||||
"""
|
||||
Create a SearchScope object after normalizing the `find_links`.
|
||||
"""
|
||||
|
|
@ -36,9 +38,9 @@ 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("~"):
|
||||
if link.startswith('~'):
|
||||
new_link = normalize_path(link)
|
||||
if os.path.exists(new_link):
|
||||
link = new_link
|
||||
|
|
@ -49,11 +51,11 @@ class SearchScope:
|
|||
if not has_tls():
|
||||
for link in itertools.chain(index_urls, built_find_links):
|
||||
parsed = urllib.parse.urlparse(link)
|
||||
if parsed.scheme == "https":
|
||||
if parsed.scheme == 'https':
|
||||
logger.warning(
|
||||
"pip is configured with locations that require "
|
||||
"TLS/SSL, however the ssl module in Python is not "
|
||||
"available."
|
||||
'pip is configured with locations that require '
|
||||
'TLS/SSL, however the ssl module in Python is not '
|
||||
'available.',
|
||||
)
|
||||
break
|
||||
|
||||
|
|
@ -64,8 +66,8 @@ class SearchScope:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
find_links: List[str],
|
||||
index_urls: List[str],
|
||||
find_links: list[str],
|
||||
index_urls: list[str],
|
||||
) -> None:
|
||||
self.find_links = find_links
|
||||
self.index_urls = index_urls
|
||||
|
|
@ -95,18 +97,18 @@ class SearchScope:
|
|||
redacted_index_urls.append(redacted_index_url)
|
||||
|
||||
lines.append(
|
||||
"Looking in indexes: {}".format(", ".join(redacted_index_urls))
|
||||
'Looking in indexes: {}'.format(', '.join(redacted_index_urls)),
|
||||
)
|
||||
|
||||
if self.find_links:
|
||||
lines.append(
|
||||
"Looking in links: {}".format(
|
||||
", ".join(redact_auth_from_url(url) for url in self.find_links)
|
||||
)
|
||||
'Looking in links: {}'.format(
|
||||
', '.join(redact_auth_from_url(url) for url in self.find_links),
|
||||
),
|
||||
)
|
||||
return "\n".join(lines)
|
||||
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
|
||||
|
|
@ -115,15 +117,15 @@ class SearchScope:
|
|||
|
||||
def mkurl_pypi_url(url: str) -> str:
|
||||
loc = posixpath.join(
|
||||
url, urllib.parse.quote(canonicalize_name(project_name))
|
||||
url, urllib.parse.quote(canonicalize_name(project_name)),
|
||||
)
|
||||
# For maximum compatibility with easy_install, ensure the path
|
||||
# ends in a trailing slash. Although this isn't in the spec
|
||||
# (and PyPI can handle it without the slash) some other index
|
||||
# implementations might break if they relied on easy_install's
|
||||
# behavior.
|
||||
if not loc.endswith("/"):
|
||||
loc = loc + "/"
|
||||
if not loc.endswith('/'):
|
||||
loc = loc + '/'
|
||||
return loc
|
||||
|
||||
return [mkurl_pypi_url(url) for url in self.index_urls]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.models.format_control import FormatControl
|
||||
|
|
@ -10,11 +12,11 @@ class SelectionPreferences:
|
|||
"""
|
||||
|
||||
__slots__ = [
|
||||
"allow_yanked",
|
||||
"allow_all_prereleases",
|
||||
"format_control",
|
||||
"prefer_binary",
|
||||
"ignore_requires_python",
|
||||
'allow_yanked',
|
||||
'allow_all_prereleases',
|
||||
'format_control',
|
||||
'prefer_binary',
|
||||
'ignore_requires_python',
|
||||
]
|
||||
|
||||
# Don't include an allow_yanked default value to make sure each call
|
||||
|
|
@ -25,9 +27,9 @@ class SelectionPreferences:
|
|||
self,
|
||||
allow_yanked: bool,
|
||||
allow_all_prereleases: bool = False,
|
||||
format_control: Optional[FormatControl] = None,
|
||||
format_control: FormatControl | None = None,
|
||||
prefer_binary: bool = False,
|
||||
ignore_requires_python: Optional[bool] = None,
|
||||
ignore_requires_python: bool | None = None,
|
||||
) -> None:
|
||||
"""Create a SelectionPreferences object.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import List, Optional, Tuple
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from pip._vendor.packaging.tags import Tag
|
||||
|
||||
from pip._internal.utils.compatibility_tags import get_supported, version_info_to_nodot
|
||||
from pip._internal.utils.compatibility_tags import get_supported
|
||||
from pip._internal.utils.compatibility_tags import version_info_to_nodot
|
||||
from pip._internal.utils.misc import normalize_version_info
|
||||
from pip._vendor.packaging.tags import Tag
|
||||
|
||||
|
||||
class TargetPython:
|
||||
|
|
@ -15,21 +19,21 @@ class TargetPython:
|
|||
"""
|
||||
|
||||
__slots__ = [
|
||||
"_given_py_version_info",
|
||||
"abis",
|
||||
"implementation",
|
||||
"platforms",
|
||||
"py_version",
|
||||
"py_version_info",
|
||||
"_valid_tags",
|
||||
'_given_py_version_info',
|
||||
'abis',
|
||||
'implementation',
|
||||
'platforms',
|
||||
'py_version',
|
||||
'py_version_info',
|
||||
'_valid_tags',
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
platforms: Optional[List[str]] = None,
|
||||
py_version_info: Optional[Tuple[int, ...]] = None,
|
||||
abis: Optional[List[str]] = None,
|
||||
implementation: Optional[str] = None,
|
||||
platforms: list[str] | None = None,
|
||||
py_version_info: tuple[int, ...] | None = None,
|
||||
abis: list[str] | None = None,
|
||||
implementation: str | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
:param platforms: A list of strings or None. If None, searches for
|
||||
|
|
@ -53,7 +57,7 @@ class TargetPython:
|
|||
else:
|
||||
py_version_info = normalize_version_info(py_version_info)
|
||||
|
||||
py_version = ".".join(map(str, py_version_info[:2]))
|
||||
py_version = '.'.join(map(str, py_version_info[:2]))
|
||||
|
||||
self.abis = abis
|
||||
self.implementation = implementation
|
||||
|
|
@ -62,7 +66,7 @@ class TargetPython:
|
|||
self.py_version_info = py_version_info
|
||||
|
||||
# This is used to cache the return value of get_tags().
|
||||
self._valid_tags: Optional[List[Tag]] = None
|
||||
self._valid_tags: list[Tag] | None = None
|
||||
|
||||
def format_given(self) -> str:
|
||||
"""
|
||||
|
|
@ -70,21 +74,21 @@ class TargetPython:
|
|||
"""
|
||||
display_version = None
|
||||
if self._given_py_version_info is not None:
|
||||
display_version = ".".join(
|
||||
display_version = '.'.join(
|
||||
str(part) for part in self._given_py_version_info
|
||||
)
|
||||
|
||||
key_values = [
|
||||
("platforms", self.platforms),
|
||||
("version_info", display_version),
|
||||
("abis", self.abis),
|
||||
("implementation", self.implementation),
|
||||
('platforms', self.platforms),
|
||||
('version_info', display_version),
|
||||
('abis', self.abis),
|
||||
('implementation', self.implementation),
|
||||
]
|
||||
return " ".join(
|
||||
f"{key}={value!r}" for key, value in key_values if value is not None
|
||||
return ' '.join(
|
||||
f'{key}={value!r}' for key, value in key_values if value is not None
|
||||
)
|
||||
|
||||
def get_tags(self) -> List[Tag]:
|
||||
def get_tags(self) -> list[Tag]:
|
||||
"""
|
||||
Return the supported PEP 425 tags to check wheel candidates against.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
"""Represents a wheel file and provides access to the various parts of the
|
||||
name that have meaning.
|
||||
"""
|
||||
import re
|
||||
from typing import Dict, Iterable, List
|
||||
from __future__ import annotations
|
||||
|
||||
from pip._vendor.packaging.tags import Tag
|
||||
import re
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
|
||||
from pip._internal.exceptions import InvalidWheelFilename
|
||||
from pip._vendor.packaging.tags import Tag
|
||||
|
||||
|
||||
class Wheel:
|
||||
|
|
@ -25,27 +28,27 @@ class Wheel:
|
|||
"""
|
||||
wheel_info = self.wheel_file_re.match(filename)
|
||||
if not wheel_info:
|
||||
raise InvalidWheelFilename(f"{filename} is not a valid wheel filename.")
|
||||
raise InvalidWheelFilename(f'{filename} is not a valid wheel filename.')
|
||||
self.filename = filename
|
||||
self.name = wheel_info.group("name").replace("_", "-")
|
||||
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(".")
|
||||
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('.')
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
|
|
@ -61,7 +64,7 @@ class Wheel:
|
|||
return min(tags.index(tag) for tag in self.file_tags if tag in tags)
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
"""Contains purely network-related utilities.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
|
|
@ -3,23 +3,27 @@
|
|||
Contains interface (MultiDomainBasicAuth) and associated glue code for
|
||||
providing credentials in the context of network requests.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import urllib.parse
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
|
||||
from pip._vendor.requests.models import Request, Response
|
||||
from pip._vendor.requests.utils import get_netrc_auth
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.utils.logging import getLogger
|
||||
from pip._internal.utils.misc import (
|
||||
ask,
|
||||
ask_input,
|
||||
ask_password,
|
||||
remove_auth_from_url,
|
||||
split_auth_netloc_from_url,
|
||||
)
|
||||
from pip._internal.utils.misc import ask
|
||||
from pip._internal.utils.misc import ask_input
|
||||
from pip._internal.utils.misc import ask_password
|
||||
from pip._internal.utils.misc import remove_auth_from_url
|
||||
from pip._internal.utils.misc import split_auth_netloc_from_url
|
||||
from pip._internal.vcs.versioncontrol import AuthInfo
|
||||
from pip._vendor.requests.auth import AuthBase
|
||||
from pip._vendor.requests.auth import HTTPBasicAuth
|
||||
from pip._vendor.requests.models import Request
|
||||
from pip._vendor.requests.models import Response
|
||||
from pip._vendor.requests.utils import get_netrc_auth
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
|
@ -31,13 +35,13 @@ except ImportError:
|
|||
keyring = None # type: ignore[assignment]
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"Keyring is skipped due to an exception: %s",
|
||||
'Keyring is skipped due to an exception: %s',
|
||||
str(exc),
|
||||
)
|
||||
keyring = None # type: ignore[assignment]
|
||||
|
||||
|
||||
def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[AuthInfo]:
|
||||
def get_keyring_auth(url: str | None, username: str | None) -> AuthInfo | None:
|
||||
"""Return the tuple auth for a given url from keyring."""
|
||||
global keyring
|
||||
if not url or not keyring:
|
||||
|
|
@ -49,21 +53,21 @@ def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[Au
|
|||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
logger.debug("Getting credentials from keyring for %s", url)
|
||||
logger.debug('Getting credentials from keyring for %s', url)
|
||||
cred = get_credential(url, username)
|
||||
if cred is not None:
|
||||
return cred.username, cred.password
|
||||
return None
|
||||
|
||||
if username:
|
||||
logger.debug("Getting password from keyring for %s", url)
|
||||
logger.debug('Getting password from keyring for %s', url)
|
||||
password = keyring.get_password(url, username)
|
||||
if password:
|
||||
return username, password
|
||||
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"Keyring is skipped due to an exception: %s",
|
||||
'Keyring is skipped due to an exception: %s',
|
||||
str(exc),
|
||||
)
|
||||
keyring = None # type: ignore[assignment]
|
||||
|
|
@ -72,19 +76,19 @@ def get_keyring_auth(url: Optional[str], username: Optional[str]) -> Optional[Au
|
|||
|
||||
class MultiDomainBasicAuth(AuthBase):
|
||||
def __init__(
|
||||
self, prompting: bool = True, index_urls: Optional[List[str]] = None
|
||||
self, prompting: bool = True, index_urls: list[str] | None = None,
|
||||
) -> None:
|
||||
self.prompting = prompting
|
||||
self.index_urls = index_urls
|
||||
self.passwords: Dict[str, AuthInfo] = {}
|
||||
self.passwords: dict[str, AuthInfo] = {}
|
||||
# When the user is prompted to enter credentials and keyring is
|
||||
# available, we will offer to save them. If the user accepts,
|
||||
# this value is set to the credentials they entered. After the
|
||||
# request authenticates, the caller should call
|
||||
# ``save_credentials`` to save these.
|
||||
self._credentials_to_save: Optional[Credentials] = None
|
||||
self._credentials_to_save: Credentials | None = None
|
||||
|
||||
def _get_index_url(self, url: str) -> Optional[str]:
|
||||
def _get_index_url(self, url: str) -> str | None:
|
||||
"""Return the original index URL matching the requested URL.
|
||||
|
||||
Cached or dynamically generated credentials may work against
|
||||
|
|
@ -101,7 +105,7 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
return None
|
||||
|
||||
for u in self.index_urls:
|
||||
prefix = remove_auth_from_url(u).rstrip("/") + "/"
|
||||
prefix = remove_auth_from_url(u).rstrip('/') + '/'
|
||||
if url.startswith(prefix):
|
||||
return u
|
||||
return None
|
||||
|
|
@ -121,7 +125,7 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
# Start with the credentials embedded in the url
|
||||
username, password = url_user_password
|
||||
if username is not None and password is not None:
|
||||
logger.debug("Found credentials in url for %s", netloc)
|
||||
logger.debug('Found credentials in url for %s', netloc)
|
||||
return url_user_password
|
||||
|
||||
# Find a matching index url for this request
|
||||
|
|
@ -131,20 +135,20 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
index_info = split_auth_netloc_from_url(index_url)
|
||||
if index_info:
|
||||
index_url, _, index_url_user_password = index_info
|
||||
logger.debug("Found index url %s", index_url)
|
||||
logger.debug('Found index url %s', index_url)
|
||||
|
||||
# If an index URL was found, try its embedded credentials
|
||||
if index_url and index_url_user_password[0] is not None:
|
||||
username, password = index_url_user_password
|
||||
if username is not None and password is not None:
|
||||
logger.debug("Found credentials in index url for %s", netloc)
|
||||
logger.debug('Found credentials in index url for %s', netloc)
|
||||
return index_url_user_password
|
||||
|
||||
# Get creds from netrc if we still don't have them
|
||||
if allow_netrc:
|
||||
netrc_auth = get_netrc_auth(original_url)
|
||||
if netrc_auth:
|
||||
logger.debug("Found credentials in netrc for %s", netloc)
|
||||
logger.debug('Found credentials in netrc for %s', netloc)
|
||||
return netrc_auth
|
||||
|
||||
# If we don't have a password and keyring is available, use it.
|
||||
|
|
@ -157,14 +161,14 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
)
|
||||
# fmt: on
|
||||
if kr_auth:
|
||||
logger.debug("Found credentials in keyring for %s", netloc)
|
||||
logger.debug('Found credentials in keyring for %s', netloc)
|
||||
return kr_auth
|
||||
|
||||
return username, password
|
||||
|
||||
def _get_url_and_credentials(
|
||||
self, original_url: str
|
||||
) -> Tuple[str, Optional[str], Optional[str]]:
|
||||
self, original_url: str,
|
||||
) -> tuple[str, str | None, str | None]:
|
||||
"""Return the credentials to use for the provided URL.
|
||||
|
||||
If allowed, netrc and keyring may be used to obtain the
|
||||
|
|
@ -195,18 +199,18 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
# this netloc will show up as "cached" in the conditional above.
|
||||
# Further, HTTPBasicAuth doesn't accept None, so it makes sense to
|
||||
# cache the value that is going to be used.
|
||||
username = username or ""
|
||||
password = password or ""
|
||||
username = username or ''
|
||||
password = password or ''
|
||||
|
||||
# Store any acquired credentials.
|
||||
self.passwords[netloc] = (username, password)
|
||||
|
||||
assert (
|
||||
# Credentials were found
|
||||
(username is not None and password is not None)
|
||||
(username is not None and password is not None) or
|
||||
# Credentials were not found
|
||||
or (username is None and password is None)
|
||||
), f"Could not load credentials from url: {original_url}"
|
||||
(username is None and password is None)
|
||||
), f'Could not load credentials from url: {original_url}'
|
||||
|
||||
return url, username, password
|
||||
|
||||
|
|
@ -222,28 +226,28 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
req = HTTPBasicAuth(username, password)(req)
|
||||
|
||||
# Attach a hook to handle 401 responses
|
||||
req.register_hook("response", self.handle_401)
|
||||
req.register_hook('response', self.handle_401)
|
||||
|
||||
return req
|
||||
|
||||
# Factored out to allow for easy patching in tests
|
||||
def _prompt_for_password(
|
||||
self, netloc: str
|
||||
) -> Tuple[Optional[str], Optional[str], bool]:
|
||||
username = ask_input(f"User for {netloc}: ")
|
||||
self, netloc: str,
|
||||
) -> tuple[str | None, str | None, bool]:
|
||||
username = ask_input(f'User for {netloc}: ')
|
||||
if not username:
|
||||
return None, None, False
|
||||
auth = get_keyring_auth(netloc, username)
|
||||
if auth and auth[0] is not None and auth[1] is not None:
|
||||
return auth[0], auth[1], False
|
||||
password = ask_password("Password: ")
|
||||
password = ask_password('Password: ')
|
||||
return username, password, True
|
||||
|
||||
# Factored out to allow for easy patching in tests
|
||||
def _should_save_password_to_keyring(self) -> bool:
|
||||
if not keyring:
|
||||
return False
|
||||
return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
|
||||
return ask('Save credentials to keyring [y/N]: ', ['y', 'n']) == 'y'
|
||||
|
||||
def handle_401(self, resp: Response, **kwargs: Any) -> Response:
|
||||
# We only care about 401 responses, anything else we want to just
|
||||
|
|
@ -284,14 +288,14 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
resp.raw.release_conn()
|
||||
|
||||
# Add our new username and password to the request
|
||||
req = HTTPBasicAuth(username or "", password or "")(resp.request)
|
||||
req.register_hook("response", self.warn_on_401)
|
||||
req = HTTPBasicAuth(username or '', password or '')(resp.request)
|
||||
req.register_hook('response', self.warn_on_401)
|
||||
|
||||
# On successful request, save the credentials that were used to
|
||||
# keyring. (Note that if the user responded "no" above, this member
|
||||
# is not set and nothing will be saved.)
|
||||
if self._credentials_to_save:
|
||||
req.register_hook("response", self.save_credentials)
|
||||
req.register_hook('response', self.save_credentials)
|
||||
|
||||
# Send our new request
|
||||
new_resp = resp.connection.send(req, **kwargs)
|
||||
|
|
@ -303,13 +307,13 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
"""Response callback to warn about incorrect credentials."""
|
||||
if resp.status_code == 401:
|
||||
logger.warning(
|
||||
"401 Error, Credentials not correct for %s",
|
||||
'401 Error, Credentials not correct for %s',
|
||||
resp.request.url,
|
||||
)
|
||||
|
||||
def save_credentials(self, resp: Response, **kwargs: Any) -> None:
|
||||
"""Response callback to save credentials on success."""
|
||||
assert keyring is not None, "should never reach here without keyring"
|
||||
assert keyring is not None, 'should never reach here without keyring'
|
||||
if not keyring:
|
||||
return
|
||||
|
||||
|
|
@ -317,7 +321,7 @@ class MultiDomainBasicAuth(AuthBase):
|
|||
self._credentials_to_save = None
|
||||
if creds and resp.status_code < 400:
|
||||
try:
|
||||
logger.info("Saving credentials to keyring")
|
||||
logger.info('Saving credentials to keyring')
|
||||
keyring.set_password(*creds)
|
||||
except Exception:
|
||||
logger.exception("Failed to save credentials")
|
||||
logger.exception('Failed to save credentials')
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
"""HTTP cache implementation.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
from typing import Iterator, Optional
|
||||
from typing import Iterator
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.utils.filesystem import adjacent_tmp_file
|
||||
from pip._internal.utils.filesystem import replace
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
from pip._vendor.cachecontrol.cache import BaseCache
|
||||
from pip._vendor.cachecontrol.caches import FileCache
|
||||
from pip._vendor.requests.models import Response
|
||||
|
||||
from pip._internal.utils.filesystem import adjacent_tmp_file, replace
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
|
||||
|
||||
def is_from_cache(response: Response) -> bool:
|
||||
return getattr(response, "from_cache", False)
|
||||
return getattr(response, 'from_cache', False)
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
|
@ -35,7 +37,7 @@ class SafeFileCache(BaseCache):
|
|||
"""
|
||||
|
||||
def __init__(self, directory: str) -> None:
|
||||
assert directory is not None, "Cache directory must not be None."
|
||||
assert directory is not None, 'Cache directory must not be None.'
|
||||
super().__init__()
|
||||
self.directory = directory
|
||||
|
||||
|
|
@ -47,13 +49,13 @@ class SafeFileCache(BaseCache):
|
|||
parts = list(hashed[:5]) + [hashed]
|
||||
return os.path.join(self.directory, *parts)
|
||||
|
||||
def get(self, key: str) -> Optional[bytes]:
|
||||
def get(self, key: str) -> bytes | None:
|
||||
path = self._get_cache_path(key)
|
||||
with suppressed_cache_errors():
|
||||
with open(path, "rb") as f:
|
||||
with open(path, 'rb') as f:
|
||||
return f.read()
|
||||
|
||||
def set(self, key: str, value: bytes, expires: Optional[int] = None) -> None:
|
||||
def set(self, key: str, value: bytes, expires: int | None = None) -> None:
|
||||
path = self._get_cache_path(key)
|
||||
with suppressed_cache_errors():
|
||||
ensure_dir(os.path.dirname(path))
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
"""Download files with progress indicators.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import cgi
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
from typing import Iterable, Optional, Tuple
|
||||
|
||||
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
|
||||
from typing import Iterable
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.cli.progress_bars import get_download_progress_renderer
|
||||
from pip._internal.exceptions import NetworkConnectionError
|
||||
|
|
@ -14,15 +16,21 @@ from pip._internal.models.index import PyPI
|
|||
from pip._internal.models.link import Link
|
||||
from pip._internal.network.cache import is_from_cache
|
||||
from pip._internal.network.session import PipSession
|
||||
from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks
|
||||
from pip._internal.utils.misc import format_size, redact_auth_from_url, splitext
|
||||
from pip._internal.network.utils import HEADERS
|
||||
from pip._internal.network.utils import raise_for_status
|
||||
from pip._internal.network.utils import response_chunks
|
||||
from pip._internal.utils.misc import format_size
|
||||
from pip._internal.utils.misc import redact_auth_from_url
|
||||
from pip._internal.utils.misc import splitext
|
||||
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE
|
||||
from pip._vendor.requests.models import Response
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_http_response_size(resp: Response) -> Optional[int]:
|
||||
def _get_http_response_size(resp: Response) -> int | None:
|
||||
try:
|
||||
return int(resp.headers["content-length"])
|
||||
return int(resp.headers['content-length'])
|
||||
except (ValueError, KeyError, TypeError):
|
||||
return None
|
||||
|
||||
|
|
@ -42,12 +50,12 @@ def _prepare_download(
|
|||
logged_url = redact_auth_from_url(url)
|
||||
|
||||
if total_length:
|
||||
logged_url = "{} ({})".format(logged_url, format_size(total_length))
|
||||
logged_url = f'{logged_url} ({format_size(total_length)})'
|
||||
|
||||
if is_from_cache(resp):
|
||||
logger.info("Using cached %s", logged_url)
|
||||
logger.info('Using cached %s', logged_url)
|
||||
else:
|
||||
logger.info("Downloading %s", logged_url)
|
||||
logger.info('Downloading %s', logged_url)
|
||||
|
||||
if logger.getEffectiveLevel() > logging.INFO:
|
||||
show_progress = False
|
||||
|
|
@ -82,7 +90,7 @@ def parse_content_disposition(content_disposition: str, default_filename: str) -
|
|||
return the default filename if the result is empty.
|
||||
"""
|
||||
_type, params = cgi.parse_header(content_disposition)
|
||||
filename = params.get("filename")
|
||||
filename = params.get('filename')
|
||||
if filename:
|
||||
# We need to sanitize the filename to prevent directory traversal
|
||||
# in case the filename contains ".." path parts.
|
||||
|
|
@ -96,12 +104,12 @@ def _get_http_response_filename(resp: Response, link: Link) -> str:
|
|||
"""
|
||||
filename = link.filename # fallback
|
||||
# Have a look at the Content-Disposition header for a better guess
|
||||
content_disposition = resp.headers.get("content-disposition")
|
||||
content_disposition = resp.headers.get('content-disposition')
|
||||
if content_disposition:
|
||||
filename = parse_content_disposition(content_disposition, filename)
|
||||
ext: Optional[str] = splitext(filename)[1]
|
||||
ext: str | None = splitext(filename)[1]
|
||||
if not ext:
|
||||
ext = mimetypes.guess_extension(resp.headers.get("content-type", ""))
|
||||
ext = mimetypes.guess_extension(resp.headers.get('content-type', ''))
|
||||
if ext:
|
||||
filename += ext
|
||||
if not ext and link.url != resp.url:
|
||||
|
|
@ -112,7 +120,7 @@ def _get_http_response_filename(resp: Response, link: Link) -> str:
|
|||
|
||||
|
||||
def _http_get_download(session: PipSession, link: Link) -> Response:
|
||||
target_url = link.url.split("#", 1)[0]
|
||||
target_url = link.url.split('#', 1)[0]
|
||||
resp = session.get(target_url, headers=HEADERS, stream=True)
|
||||
raise_for_status(resp)
|
||||
return resp
|
||||
|
|
@ -127,14 +135,14 @@ class Downloader:
|
|||
self._session = session
|
||||
self._progress_bar = progress_bar
|
||||
|
||||
def __call__(self, link: Link, location: str) -> Tuple[str, str]:
|
||||
def __call__(self, link: Link, location: str) -> tuple[str, str]:
|
||||
"""Download the file given by link into location."""
|
||||
try:
|
||||
resp = _http_get_download(self._session, link)
|
||||
except NetworkConnectionError as e:
|
||||
assert e.response is not None
|
||||
logger.critical(
|
||||
"HTTP error %s while getting %s", e.response.status_code, link
|
||||
'HTTP error %s while getting %s', e.response.status_code, link,
|
||||
)
|
||||
raise
|
||||
|
||||
|
|
@ -142,10 +150,10 @@ class Downloader:
|
|||
filepath = os.path.join(location, filename)
|
||||
|
||||
chunks = _prepare_download(resp, link, self._progress_bar)
|
||||
with open(filepath, "wb") as content_file:
|
||||
with open(filepath, 'wb') as content_file:
|
||||
for chunk in chunks:
|
||||
content_file.write(chunk)
|
||||
content_type = resp.headers.get("Content-Type", "")
|
||||
content_type = resp.headers.get('Content-Type', '')
|
||||
return filepath, content_type
|
||||
|
||||
|
||||
|
|
@ -159,8 +167,8 @@ class BatchDownloader:
|
|||
self._progress_bar = progress_bar
|
||||
|
||||
def __call__(
|
||||
self, links: Iterable[Link], location: str
|
||||
) -> Iterable[Tuple[Link, Tuple[str, str]]]:
|
||||
self, links: Iterable[Link], location: str,
|
||||
) -> Iterable[tuple[Link, tuple[str, str]]]:
|
||||
"""Download the files given by links into location."""
|
||||
for link in links:
|
||||
try:
|
||||
|
|
@ -168,7 +176,7 @@ class BatchDownloader:
|
|||
except NetworkConnectionError as e:
|
||||
assert e.response is not None
|
||||
logger.critical(
|
||||
"HTTP error %s while getting %s",
|
||||
'HTTP error %s while getting %s',
|
||||
e.response.status_code,
|
||||
link,
|
||||
)
|
||||
|
|
@ -178,8 +186,8 @@ class BatchDownloader:
|
|||
filepath = os.path.join(location, filename)
|
||||
|
||||
chunks = _prepare_download(resp, link, self._progress_bar)
|
||||
with open(filepath, "wb") as content_file:
|
||||
with open(filepath, 'wb') as content_file:
|
||||
for chunk in chunks:
|
||||
content_file.write(chunk)
|
||||
content_type = resp.headers.get("Content-Type", "")
|
||||
content_type = resp.headers.get('Content-Type', '')
|
||||
yield link, (filepath, content_type)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Lazy ZIP over HTTP"""
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = ["HTTPRangeRequestUnsupported", "dist_from_wheel_url"]
|
||||
__all__ = ['HTTPRangeRequestUnsupported', 'dist_from_wheel_url']
|
||||
|
||||
from bisect import bisect_left, bisect_right
|
||||
from contextlib import contextmanager
|
||||
|
|
@ -47,25 +48,25 @@ class LazyZipOverHTTP:
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self, url: str, session: PipSession, chunk_size: int = CONTENT_CHUNK_SIZE
|
||||
self, url: str, session: PipSession, chunk_size: int = CONTENT_CHUNK_SIZE,
|
||||
) -> None:
|
||||
head = session.head(url, headers=HEADERS)
|
||||
raise_for_status(head)
|
||||
assert head.status_code == 200
|
||||
self._session, self._url, self._chunk_size = session, url, chunk_size
|
||||
self._length = int(head.headers["Content-Length"])
|
||||
self._length = int(head.headers['Content-Length'])
|
||||
self._file = NamedTemporaryFile()
|
||||
self.truncate(self._length)
|
||||
self._left: List[int] = []
|
||||
self._right: List[int] = []
|
||||
if "bytes" not in head.headers.get("Accept-Ranges", "none"):
|
||||
raise HTTPRangeRequestUnsupported("range request is not supported")
|
||||
self._left: list[int] = []
|
||||
self._right: list[int] = []
|
||||
if 'bytes' not in head.headers.get('Accept-Ranges', 'none'):
|
||||
raise HTTPRangeRequestUnsupported('range request is not supported')
|
||||
self._check_zip()
|
||||
|
||||
@property
|
||||
def mode(self) -> str:
|
||||
"""Opening mode, which is always rb."""
|
||||
return "rb"
|
||||
return 'rb'
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
|
@ -117,7 +118,7 @@ class LazyZipOverHTTP:
|
|||
"""Return the current position."""
|
||||
return self._file.tell()
|
||||
|
||||
def truncate(self, size: Optional[int] = None) -> int:
|
||||
def truncate(self, size: int | None = None) -> int:
|
||||
"""Resize the stream to the given size in bytes.
|
||||
|
||||
If size is unspecified resize to the current position.
|
||||
|
|
@ -131,11 +132,11 @@ class LazyZipOverHTTP:
|
|||
"""Return False."""
|
||||
return False
|
||||
|
||||
def __enter__(self) -> "LazyZipOverHTTP":
|
||||
def __enter__(self) -> LazyZipOverHTTP:
|
||||
self._file.__enter__()
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc: Any) -> Optional[bool]:
|
||||
def __exit__(self, *exc: Any) -> bool | None:
|
||||
return self._file.__exit__(*exc)
|
||||
|
||||
@contextmanager
|
||||
|
|
@ -166,18 +167,18 @@ class LazyZipOverHTTP:
|
|||
break
|
||||
|
||||
def _stream_response(
|
||||
self, start: int, end: int, base_headers: Dict[str, str] = HEADERS
|
||||
self, start: int, end: int, base_headers: dict[str, str] = HEADERS,
|
||||
) -> Response:
|
||||
"""Return HTTP response to a range request from start to end."""
|
||||
headers = base_headers.copy()
|
||||
headers["Range"] = f"bytes={start}-{end}"
|
||||
headers['Range'] = f'bytes={start}-{end}'
|
||||
# TODO: Get range requests to be correctly cached
|
||||
headers["Cache-Control"] = "no-cache"
|
||||
headers['Cache-Control'] = 'no-cache'
|
||||
return self._session.get(self._url, headers=headers, stream=True)
|
||||
|
||||
def _merge(
|
||||
self, start: int, end: int, left: int, right: int
|
||||
) -> Iterator[Tuple[int, int]]:
|
||||
self, start: int, end: int, left: int, right: int,
|
||||
) -> Iterator[tuple[int, int]]:
|
||||
"""Return an iterator of intervals to be fetched.
|
||||
|
||||
Args:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""PipSession and supporting code, containing all pip-specific
|
||||
network request configuration and behavior.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import email.utils
|
||||
import io
|
||||
|
|
@ -15,27 +16,37 @@ import subprocess
|
|||
import sys
|
||||
import urllib.parse
|
||||
import warnings
|
||||
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Tuple, Union
|
||||
|
||||
from pip._vendor import requests, urllib3
|
||||
from pip._vendor.cachecontrol import CacheControlAdapter
|
||||
from pip._vendor.requests.adapters import BaseAdapter, HTTPAdapter
|
||||
from pip._vendor.requests.models import PreparedRequest, Response
|
||||
from pip._vendor.requests.structures import CaseInsensitiveDict
|
||||
from pip._vendor.urllib3.connectionpool import ConnectionPool
|
||||
from pip._vendor.urllib3.exceptions import InsecureRequestWarning
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from pip import __version__
|
||||
from pip._internal.metadata import get_default_environment
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.network.auth import MultiDomainBasicAuth
|
||||
from pip._internal.network.cache import SafeFileCache
|
||||
|
||||
# Import ssl from compat so the initial import occurs in only one place.
|
||||
from pip._internal.utils.compat import has_tls
|
||||
from pip._internal.utils.glibc import libc_ver
|
||||
from pip._internal.utils.misc import build_url_from_netloc, parse_netloc
|
||||
from pip._internal.utils.misc import build_url_from_netloc
|
||||
from pip._internal.utils.misc import parse_netloc
|
||||
from pip._internal.utils.urls import url_to_path
|
||||
from pip._vendor import requests
|
||||
from pip._vendor import urllib3
|
||||
from pip._vendor.cachecontrol import CacheControlAdapter
|
||||
from pip._vendor.requests.adapters import BaseAdapter
|
||||
from pip._vendor.requests.adapters import HTTPAdapter
|
||||
from pip._vendor.requests.models import PreparedRequest
|
||||
from pip._vendor.requests.models import Response
|
||||
from pip._vendor.requests.structures import CaseInsensitiveDict
|
||||
from pip._vendor.urllib3.connectionpool import ConnectionPool
|
||||
from pip._vendor.urllib3.exceptions import InsecureRequestWarning
|
||||
# Import ssl from compat so the initial import occurs in only one place.
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -43,19 +54,19 @@ SecureOrigin = Tuple[str, str, Optional[Union[int, str]]]
|
|||
|
||||
|
||||
# Ignore warning raised when using --trusted-host.
|
||||
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
|
||||
warnings.filterwarnings('ignore', category=InsecureRequestWarning)
|
||||
|
||||
|
||||
SECURE_ORIGINS: List[SecureOrigin] = [
|
||||
SECURE_ORIGINS: list[SecureOrigin] = [
|
||||
# protocol, hostname, port
|
||||
# Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC)
|
||||
("https", "*", "*"),
|
||||
("*", "localhost", "*"),
|
||||
("*", "127.0.0.0/8", "*"),
|
||||
("*", "::1/128", "*"),
|
||||
("file", "*", None),
|
||||
('https', '*', '*'),
|
||||
('*', 'localhost', '*'),
|
||||
('*', '127.0.0.0/8', '*'),
|
||||
('*', '::1/128', '*'),
|
||||
('file', '*', None),
|
||||
# ssh is always secure.
|
||||
("ssh", "*", "*"),
|
||||
('ssh', '*', '*'),
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -68,13 +79,13 @@ SECURE_ORIGINS: List[SecureOrigin] = [
|
|||
# For more background, see: https://github.com/pypa/pip/issues/5499
|
||||
CI_ENVIRONMENT_VARIABLES = (
|
||||
# Azure Pipelines
|
||||
"BUILD_BUILDID",
|
||||
'BUILD_BUILDID',
|
||||
# Jenkins
|
||||
"BUILD_ID",
|
||||
'BUILD_ID',
|
||||
# AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI
|
||||
"CI",
|
||||
'CI',
|
||||
# Explicit environment variable.
|
||||
"PIP_IS_CI",
|
||||
'PIP_IS_CI',
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -92,100 +103,100 @@ def user_agent() -> str:
|
|||
"""
|
||||
Return a string representing the user agent.
|
||||
"""
|
||||
data: Dict[str, Any] = {
|
||||
"installer": {"name": "pip", "version": __version__},
|
||||
"python": platform.python_version(),
|
||||
"implementation": {
|
||||
"name": platform.python_implementation(),
|
||||
data: dict[str, Any] = {
|
||||
'installer': {'name': 'pip', 'version': __version__},
|
||||
'python': platform.python_version(),
|
||||
'implementation': {
|
||||
'name': platform.python_implementation(),
|
||||
},
|
||||
}
|
||||
|
||||
if data["implementation"]["name"] == "CPython":
|
||||
data["implementation"]["version"] = platform.python_version()
|
||||
elif data["implementation"]["name"] == "PyPy":
|
||||
if data['implementation']['name'] == 'CPython':
|
||||
data['implementation']['version'] = platform.python_version()
|
||||
elif data['implementation']['name'] == 'PyPy':
|
||||
pypy_version_info = sys.pypy_version_info # type: ignore
|
||||
if pypy_version_info.releaselevel == "final":
|
||||
if pypy_version_info.releaselevel == 'final':
|
||||
pypy_version_info = pypy_version_info[:3]
|
||||
data["implementation"]["version"] = ".".join(
|
||||
[str(x) for x in pypy_version_info]
|
||||
data['implementation']['version'] = '.'.join(
|
||||
[str(x) for x in pypy_version_info],
|
||||
)
|
||||
elif data["implementation"]["name"] == "Jython":
|
||||
elif data['implementation']['name'] == 'Jython':
|
||||
# Complete Guess
|
||||
data["implementation"]["version"] = platform.python_version()
|
||||
elif data["implementation"]["name"] == "IronPython":
|
||||
data['implementation']['version'] = platform.python_version()
|
||||
elif data['implementation']['name'] == 'IronPython':
|
||||
# Complete Guess
|
||||
data["implementation"]["version"] = platform.python_version()
|
||||
data['implementation']['version'] = platform.python_version()
|
||||
|
||||
if sys.platform.startswith("linux"):
|
||||
if sys.platform.startswith('linux'):
|
||||
from pip._vendor import distro
|
||||
|
||||
linux_distribution = distro.name(), distro.version(), distro.codename()
|
||||
distro_infos: Dict[str, Any] = dict(
|
||||
distro_infos: dict[str, Any] = dict(
|
||||
filter(
|
||||
lambda x: x[1],
|
||||
zip(["name", "version", "id"], linux_distribution),
|
||||
)
|
||||
zip(['name', 'version', 'id'], linux_distribution),
|
||||
),
|
||||
)
|
||||
libc = dict(
|
||||
filter(
|
||||
lambda x: x[1],
|
||||
zip(["lib", "version"], libc_ver()),
|
||||
)
|
||||
zip(['lib', 'version'], libc_ver()),
|
||||
),
|
||||
)
|
||||
if libc:
|
||||
distro_infos["libc"] = libc
|
||||
distro_infos['libc'] = libc
|
||||
if distro_infos:
|
||||
data["distro"] = distro_infos
|
||||
data['distro'] = distro_infos
|
||||
|
||||
if sys.platform.startswith("darwin") and platform.mac_ver()[0]:
|
||||
data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]}
|
||||
if sys.platform.startswith('darwin') and platform.mac_ver()[0]:
|
||||
data['distro'] = {'name': 'macOS', 'version': platform.mac_ver()[0]}
|
||||
|
||||
if platform.system():
|
||||
data.setdefault("system", {})["name"] = platform.system()
|
||||
data.setdefault('system', {})['name'] = platform.system()
|
||||
|
||||
if platform.release():
|
||||
data.setdefault("system", {})["release"] = platform.release()
|
||||
data.setdefault('system', {})['release'] = platform.release()
|
||||
|
||||
if platform.machine():
|
||||
data["cpu"] = platform.machine()
|
||||
data['cpu'] = platform.machine()
|
||||
|
||||
if has_tls():
|
||||
import _ssl as ssl
|
||||
|
||||
data["openssl_version"] = ssl.OPENSSL_VERSION
|
||||
data['openssl_version'] = ssl.OPENSSL_VERSION
|
||||
|
||||
setuptools_dist = get_default_environment().get_distribution("setuptools")
|
||||
setuptools_dist = get_default_environment().get_distribution('setuptools')
|
||||
if setuptools_dist is not None:
|
||||
data["setuptools_version"] = str(setuptools_dist.version)
|
||||
data['setuptools_version'] = str(setuptools_dist.version)
|
||||
|
||||
if shutil.which("rustc") is not None:
|
||||
if shutil.which('rustc') is not None:
|
||||
# If for any reason `rustc --version` fails, silently ignore it
|
||||
try:
|
||||
rustc_output = subprocess.check_output(
|
||||
["rustc", "--version"], stderr=subprocess.STDOUT, timeout=0.5
|
||||
['rustc', '--version'], stderr=subprocess.STDOUT, timeout=0.5,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
if rustc_output.startswith(b"rustc "):
|
||||
if rustc_output.startswith(b'rustc '):
|
||||
# The format of `rustc --version` is:
|
||||
# `b'rustc 1.52.1 (9bc8c42bb 2021-05-09)\n'`
|
||||
# We extract just the middle (1.52.1) part
|
||||
data["rustc_version"] = rustc_output.split(b" ")[1].decode()
|
||||
data['rustc_version'] = rustc_output.split(b' ')[1].decode()
|
||||
|
||||
# Use None rather than False so as not to give the impression that
|
||||
# pip knows it is not being run under CI. Rather, it is a null or
|
||||
# inconclusive result. Also, we include some value rather than no
|
||||
# value to make it easier to know that the check has been run.
|
||||
data["ci"] = True if looks_like_ci() else None
|
||||
data['ci'] = True if looks_like_ci() else None
|
||||
|
||||
user_data = os.environ.get("PIP_USER_AGENT_USER_DATA")
|
||||
user_data = os.environ.get('PIP_USER_AGENT_USER_DATA')
|
||||
if user_data is not None:
|
||||
data["user_data"] = user_data
|
||||
data['user_data'] = user_data
|
||||
|
||||
return "{data[installer][name]}/{data[installer][version]} {json}".format(
|
||||
return '{data[installer][name]}/{data[installer][version]} {json}'.format(
|
||||
data=data,
|
||||
json=json.dumps(data, separators=(",", ":"), sort_keys=True),
|
||||
json=json.dumps(data, separators=(',', ':'), sort_keys=True),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -194,10 +205,10 @@ class LocalFSAdapter(BaseAdapter):
|
|||
self,
|
||||
request: PreparedRequest,
|
||||
stream: bool = False,
|
||||
timeout: Optional[Union[float, Tuple[float, float]]] = None,
|
||||
verify: Union[bool, str] = True,
|
||||
cert: Optional[Union[str, Tuple[str, str]]] = None,
|
||||
proxies: Optional[Mapping[str, str]] = None,
|
||||
timeout: float | tuple[float, float] | None = None,
|
||||
verify: bool | str = True,
|
||||
cert: str | tuple[str, str] | None = None,
|
||||
proxies: Mapping[str, str] | None = None,
|
||||
) -> Response:
|
||||
pathname = url_to_path(request.url)
|
||||
|
||||
|
|
@ -212,19 +223,19 @@ class LocalFSAdapter(BaseAdapter):
|
|||
# to return a better error message:
|
||||
resp.status_code = 404
|
||||
resp.reason = type(exc).__name__
|
||||
resp.raw = io.BytesIO(f"{resp.reason}: {exc}".encode("utf8"))
|
||||
resp.raw = io.BytesIO(f'{resp.reason}: {exc}'.encode())
|
||||
else:
|
||||
modified = email.utils.formatdate(stats.st_mtime, usegmt=True)
|
||||
content_type = mimetypes.guess_type(pathname)[0] or "text/plain"
|
||||
content_type = mimetypes.guess_type(pathname)[0] or 'text/plain'
|
||||
resp.headers = CaseInsensitiveDict(
|
||||
{
|
||||
"Content-Type": content_type,
|
||||
"Content-Length": stats.st_size,
|
||||
"Last-Modified": modified,
|
||||
}
|
||||
'Content-Type': content_type,
|
||||
'Content-Length': stats.st_size,
|
||||
'Last-Modified': modified,
|
||||
},
|
||||
)
|
||||
|
||||
resp.raw = open(pathname, "rb")
|
||||
resp.raw = open(pathname, 'rb')
|
||||
resp.close = resp.raw.close
|
||||
|
||||
return resp
|
||||
|
|
@ -238,8 +249,8 @@ class InsecureHTTPAdapter(HTTPAdapter):
|
|||
self,
|
||||
conn: ConnectionPool,
|
||||
url: str,
|
||||
verify: Union[bool, str],
|
||||
cert: Optional[Union[str, Tuple[str, str]]],
|
||||
verify: bool | str,
|
||||
cert: str | tuple[str, str] | None,
|
||||
) -> None:
|
||||
super().cert_verify(conn=conn, url=url, verify=False, cert=cert)
|
||||
|
||||
|
|
@ -249,23 +260,23 @@ class InsecureCacheControlAdapter(CacheControlAdapter):
|
|||
self,
|
||||
conn: ConnectionPool,
|
||||
url: str,
|
||||
verify: Union[bool, str],
|
||||
cert: Optional[Union[str, Tuple[str, str]]],
|
||||
verify: bool | str,
|
||||
cert: str | tuple[str, str] | None,
|
||||
) -> None:
|
||||
super().cert_verify(conn=conn, url=url, verify=False, cert=cert)
|
||||
|
||||
|
||||
class PipSession(requests.Session):
|
||||
|
||||
timeout: Optional[int] = None
|
||||
timeout: int | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args: Any,
|
||||
retries: int = 0,
|
||||
cache: Optional[str] = None,
|
||||
cache: str | None = None,
|
||||
trusted_hosts: Sequence[str] = (),
|
||||
index_urls: Optional[List[str]] = None,
|
||||
index_urls: list[str] | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -276,10 +287,10 @@ class PipSession(requests.Session):
|
|||
|
||||
# Namespace the attribute with "pip_" just in case to prevent
|
||||
# possible conflicts with the base class.
|
||||
self.pip_trusted_origins: List[Tuple[str, Optional[int]]] = []
|
||||
self.pip_trusted_origins: list[tuple[str, int | None]] = []
|
||||
|
||||
# Attach our User Agent to the request
|
||||
self.headers["User-Agent"] = user_agent()
|
||||
self.headers['User-Agent'] = user_agent()
|
||||
|
||||
# Attach our Authentication handler to the session
|
||||
self.auth = MultiDomainBasicAuth(index_urls=index_urls)
|
||||
|
|
@ -327,16 +338,16 @@ class PipSession(requests.Session):
|
|||
secure_adapter = HTTPAdapter(max_retries=retries)
|
||||
self._trusted_host_adapter = insecure_adapter
|
||||
|
||||
self.mount("https://", secure_adapter)
|
||||
self.mount("http://", insecure_adapter)
|
||||
self.mount('https://', secure_adapter)
|
||||
self.mount('http://', insecure_adapter)
|
||||
|
||||
# Enable file:// urls
|
||||
self.mount("file://", LocalFSAdapter())
|
||||
self.mount('file://', LocalFSAdapter())
|
||||
|
||||
for host in trusted_hosts:
|
||||
self.add_trusted_host(host, suppress_logging=True)
|
||||
|
||||
def update_index_urls(self, new_index_urls: List[str]) -> None:
|
||||
def update_index_urls(self, new_index_urls: list[str]) -> None:
|
||||
"""
|
||||
:param new_index_urls: New index urls to update the authentication
|
||||
handler with.
|
||||
|
|
@ -344,7 +355,7 @@ class PipSession(requests.Session):
|
|||
self.auth.index_urls = new_index_urls
|
||||
|
||||
def add_trusted_host(
|
||||
self, host: str, source: Optional[str] = None, suppress_logging: bool = False
|
||||
self, host: str, source: str | None = None, suppress_logging: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
:param host: It is okay to provide a host that has previously been
|
||||
|
|
@ -353,9 +364,9 @@ class PipSession(requests.Session):
|
|||
string came from.
|
||||
"""
|
||||
if not suppress_logging:
|
||||
msg = f"adding trusted host: {host!r}"
|
||||
msg = f'adding trusted host: {host!r}'
|
||||
if source is not None:
|
||||
msg += f" (from {source})"
|
||||
msg += f' (from {source})'
|
||||
logger.info(msg)
|
||||
|
||||
host_port = parse_netloc(host)
|
||||
|
|
@ -363,21 +374,21 @@ class PipSession(requests.Session):
|
|||
self.pip_trusted_origins.append(host_port)
|
||||
|
||||
self.mount(
|
||||
build_url_from_netloc(host, scheme="http") + "/", self._trusted_host_adapter
|
||||
build_url_from_netloc(host, scheme='http') + '/', self._trusted_host_adapter,
|
||||
)
|
||||
self.mount(build_url_from_netloc(host) + "/", self._trusted_host_adapter)
|
||||
self.mount(build_url_from_netloc(host) + '/', self._trusted_host_adapter)
|
||||
if not host_port[1]:
|
||||
self.mount(
|
||||
build_url_from_netloc(host, scheme="http") + ":",
|
||||
build_url_from_netloc(host, scheme='http') + ':',
|
||||
self._trusted_host_adapter,
|
||||
)
|
||||
# Mount wildcard ports for the same host.
|
||||
self.mount(build_url_from_netloc(host) + ":", self._trusted_host_adapter)
|
||||
self.mount(build_url_from_netloc(host) + ':', self._trusted_host_adapter)
|
||||
|
||||
def iter_secure_origins(self) -> Iterator[SecureOrigin]:
|
||||
yield from SECURE_ORIGINS
|
||||
for host, port in self.pip_trusted_origins:
|
||||
yield ("*", host, "*" if port is None else port)
|
||||
yield ('*', host, '*' if port is None else port)
|
||||
|
||||
def is_secure_origin(self, location: Link) -> bool:
|
||||
# Determine if this url used a secure transport mechanism
|
||||
|
|
@ -392,14 +403,14 @@ class PipSession(requests.Session):
|
|||
# Don't count the repository type as part of the protocol: in
|
||||
# cases such as "git+ssh", only use "ssh". (I.e., Only verify against
|
||||
# the last scheme.)
|
||||
origin_protocol = origin_protocol.rsplit("+", 1)[-1]
|
||||
origin_protocol = origin_protocol.rsplit('+', 1)[-1]
|
||||
|
||||
# Determine if our origin is a secure origin by looking through our
|
||||
# hardcoded list of secure origins, as well as any additional ones
|
||||
# configured on this PackageFinder instance.
|
||||
for secure_origin in self.iter_secure_origins():
|
||||
secure_protocol, secure_host, secure_port = secure_origin
|
||||
if origin_protocol != secure_protocol and secure_protocol != "*":
|
||||
if origin_protocol != secure_protocol and secure_protocol != '*':
|
||||
continue
|
||||
|
||||
try:
|
||||
|
|
@ -409,9 +420,9 @@ class PipSession(requests.Session):
|
|||
# We don't have both a valid address or a valid network, so
|
||||
# we'll check this origin against hostnames.
|
||||
if (
|
||||
origin_host
|
||||
and origin_host.lower() != secure_host.lower()
|
||||
and secure_host != "*"
|
||||
origin_host and
|
||||
origin_host.lower() != secure_host.lower() and
|
||||
secure_host != '*'
|
||||
):
|
||||
continue
|
||||
else:
|
||||
|
|
@ -422,9 +433,9 @@ class PipSession(requests.Session):
|
|||
|
||||
# Check to see if the port matches.
|
||||
if (
|
||||
origin_port != secure_port
|
||||
and secure_port != "*"
|
||||
and secure_port is not None
|
||||
origin_port != secure_port and
|
||||
secure_port != '*' and
|
||||
secure_port is not None
|
||||
):
|
||||
continue
|
||||
|
||||
|
|
@ -436,9 +447,9 @@ class PipSession(requests.Session):
|
|||
# will not accept it as a valid location to search. We will however
|
||||
# log a warning that we are ignoring it.
|
||||
logger.warning(
|
||||
"The repository located at %s is not a trusted or secure host and "
|
||||
"is being ignored. If this repository is available via HTTPS we "
|
||||
"recommend you use HTTPS instead, otherwise you may silence "
|
||||
'The repository located at %s is not a trusted or secure host and '
|
||||
'is being ignored. If this repository is available via HTTPS we '
|
||||
'recommend you use HTTPS instead, otherwise you may silence '
|
||||
"this warning and allow it anyway with '--trusted-host %s'.",
|
||||
origin_host,
|
||||
origin_host,
|
||||
|
|
@ -448,7 +459,7 @@ class PipSession(requests.Session):
|
|||
|
||||
def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response:
|
||||
# Allow setting a default timeout on a session
|
||||
kwargs.setdefault("timeout", self.timeout)
|
||||
kwargs.setdefault('timeout', self.timeout)
|
||||
|
||||
# Dispatch the actual request
|
||||
return super().request(method, url, *args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
from typing import Dict, Iterator
|
||||
from __future__ import annotations
|
||||
|
||||
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
|
||||
from typing import Dict
|
||||
from typing import Iterator
|
||||
|
||||
from pip._internal.exceptions import NetworkConnectionError
|
||||
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE
|
||||
from pip._vendor.requests.models import Response
|
||||
|
||||
# The following comments and HTTP headers were originally added by
|
||||
# Donald Stufft in git commit 22c562429a61bb77172039e480873fb239dd8c03.
|
||||
|
|
@ -23,31 +26,31 @@ from pip._internal.exceptions import NetworkConnectionError
|
|||
# you're not asking for a compressed file and will then decompress it
|
||||
# before sending because if that's the case I don't think it'll ever be
|
||||
# possible to make this work.
|
||||
HEADERS: Dict[str, str] = {"Accept-Encoding": "identity"}
|
||||
HEADERS: dict[str, str] = {'Accept-Encoding': 'identity'}
|
||||
|
||||
|
||||
def raise_for_status(resp: Response) -> None:
|
||||
http_error_msg = ""
|
||||
http_error_msg = ''
|
||||
if isinstance(resp.reason, bytes):
|
||||
# We attempt to decode utf-8 first because some servers
|
||||
# choose to localize their reason strings. If the string
|
||||
# isn't utf-8, we fall back to iso-8859-1 for all other
|
||||
# encodings.
|
||||
try:
|
||||
reason = resp.reason.decode("utf-8")
|
||||
reason = resp.reason.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
reason = resp.reason.decode("iso-8859-1")
|
||||
reason = resp.reason.decode('iso-8859-1')
|
||||
else:
|
||||
reason = resp.reason
|
||||
|
||||
if 400 <= resp.status_code < 500:
|
||||
http_error_msg = (
|
||||
f"{resp.status_code} Client Error: {reason} for url: {resp.url}"
|
||||
f'{resp.status_code} Client Error: {reason} for url: {resp.url}'
|
||||
)
|
||||
|
||||
elif 500 <= resp.status_code < 600:
|
||||
http_error_msg = (
|
||||
f"{resp.status_code} Server Error: {reason} for url: {resp.url}"
|
||||
f'{resp.status_code} Server Error: {reason} for url: {resp.url}'
|
||||
)
|
||||
|
||||
if http_error_msg:
|
||||
|
|
@ -55,7 +58,7 @@ def raise_for_status(resp: Response) -> None:
|
|||
|
||||
|
||||
def response_chunks(
|
||||
response: Response, chunk_size: int = CONTENT_CHUNK_SIZE
|
||||
response: Response, chunk_size: int = CONTENT_CHUNK_SIZE,
|
||||
) -> Iterator[bytes]:
|
||||
"""Given a requests Response, provide the data chunks."""
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
"""xmlrpclib.Transport implementation
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import urllib.parse
|
||||
import xmlrpc.client
|
||||
from typing import TYPE_CHECKING, Tuple
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pip._internal.exceptions import NetworkConnectionError
|
||||
from pip._internal.network.session import PipSession
|
||||
|
|
@ -22,7 +24,7 @@ class PipXmlrpcTransport(xmlrpc.client.Transport):
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self, index_url: str, session: PipSession, use_datetime: bool = False
|
||||
self, index_url: str, session: PipSession, use_datetime: bool = False,
|
||||
) -> None:
|
||||
super().__init__(use_datetime)
|
||||
index_parts = urllib.parse.urlparse(index_url)
|
||||
|
|
@ -31,16 +33,16 @@ class PipXmlrpcTransport(xmlrpc.client.Transport):
|
|||
|
||||
def request(
|
||||
self,
|
||||
host: "_HostType",
|
||||
host: _HostType,
|
||||
handler: str,
|
||||
request_body: bytes,
|
||||
verbose: bool = False,
|
||||
) -> Tuple["_Marshallable", ...]:
|
||||
) -> tuple[_Marshallable, ...]:
|
||||
assert isinstance(host, str)
|
||||
parts = (self._scheme, host, handler, None, None, None)
|
||||
url = urllib.parse.urlunparse(parts)
|
||||
try:
|
||||
headers = {"Content-Type": "text/xml"}
|
||||
headers = {'Content-Type': 'text/xml'}
|
||||
response = self._session.post(
|
||||
url,
|
||||
data=request_body,
|
||||
|
|
@ -53,7 +55,7 @@ class PipXmlrpcTransport(xmlrpc.client.Transport):
|
|||
except NetworkConnectionError as exc:
|
||||
assert exc.response
|
||||
logger.critical(
|
||||
"HTTP error %s while getting %s",
|
||||
'HTTP error %s while getting %s',
|
||||
exc.response.status_code,
|
||||
url,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,27 +1,25 @@
|
|||
"""Metadata generation logic for source distributions.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.exceptions import (
|
||||
InstallationSubprocessError,
|
||||
MetadataGenerationFailed,
|
||||
)
|
||||
from pip._internal.exceptions import InstallationSubprocessError
|
||||
from pip._internal.exceptions import MetadataGenerationFailed
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
|
||||
def generate_metadata(
|
||||
build_env: BuildEnvironment, backend: Pep517HookCaller, details: str
|
||||
build_env: BuildEnvironment, backend: Pep517HookCaller, details: str,
|
||||
) -> str:
|
||||
"""Generate metadata using mechanisms described in PEP 517.
|
||||
|
||||
Returns the generated metadata directory.
|
||||
"""
|
||||
metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True)
|
||||
metadata_tmpdir = TempDirectory(kind='modern-metadata', globally_managed=True)
|
||||
|
||||
metadata_dir = metadata_tmpdir.path
|
||||
|
||||
|
|
@ -29,7 +27,7 @@ def generate_metadata(
|
|||
# Note that Pep517HookCaller implements a fallback for
|
||||
# prepare_metadata_for_build_wheel, so we don't have to
|
||||
# consider the possibility that this hook doesn't exist.
|
||||
runner = runner_with_spinner_message("Preparing metadata (pyproject.toml)")
|
||||
runner = runner_with_spinner_message('Preparing metadata (pyproject.toml)')
|
||||
with backend.subprocess_runner(runner):
|
||||
try:
|
||||
distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir)
|
||||
|
|
|
|||
|
|
@ -1,27 +1,25 @@
|
|||
"""Metadata generation logic for source distributions.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.exceptions import (
|
||||
InstallationSubprocessError,
|
||||
MetadataGenerationFailed,
|
||||
)
|
||||
from pip._internal.exceptions import InstallationSubprocessError
|
||||
from pip._internal.exceptions import MetadataGenerationFailed
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
|
||||
def generate_editable_metadata(
|
||||
build_env: BuildEnvironment, backend: Pep517HookCaller, details: str
|
||||
build_env: BuildEnvironment, backend: Pep517HookCaller, details: str,
|
||||
) -> str:
|
||||
"""Generate metadata using mechanisms described in PEP 660.
|
||||
|
||||
Returns the generated metadata directory.
|
||||
"""
|
||||
metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True)
|
||||
metadata_tmpdir = TempDirectory(kind='modern-metadata', globally_managed=True)
|
||||
|
||||
metadata_dir = metadata_tmpdir.path
|
||||
|
||||
|
|
@ -30,7 +28,7 @@ def generate_editable_metadata(
|
|||
# prepare_metadata_for_build_wheel/editable, so we don't have to
|
||||
# consider the possibility that this hook doesn't exist.
|
||||
runner = runner_with_spinner_message(
|
||||
"Preparing editable metadata (pyproject.toml)"
|
||||
'Preparing editable metadata (pyproject.toml)',
|
||||
)
|
||||
with backend.subprocess_runner(runner):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
"""Metadata generation logic for legacy source distributions.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.cli.spinners import open_spinner
|
||||
from pip._internal.exceptions import (
|
||||
InstallationError,
|
||||
InstallationSubprocessError,
|
||||
MetadataGenerationFailed,
|
||||
)
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import InstallationSubprocessError
|
||||
from pip._internal.exceptions import MetadataGenerationFailed
|
||||
from pip._internal.utils.setuptools_build import make_setuptools_egg_info_args
|
||||
from pip._internal.utils.subprocess import call_subprocess
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
|
@ -20,14 +19,14 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
def _find_egg_info(directory: str) -> str:
|
||||
"""Find an .egg-info subdirectory in `directory`."""
|
||||
filenames = [f for f in os.listdir(directory) if f.endswith(".egg-info")]
|
||||
filenames = [f for f in os.listdir(directory) if f.endswith('.egg-info')]
|
||||
|
||||
if not filenames:
|
||||
raise InstallationError(f"No .egg-info directory found in {directory}")
|
||||
raise InstallationError(f'No .egg-info directory found in {directory}')
|
||||
|
||||
if len(filenames) > 1:
|
||||
raise InstallationError(
|
||||
"More than one .egg-info directory found in {}".format(directory)
|
||||
f'More than one .egg-info directory found in {directory}',
|
||||
)
|
||||
|
||||
return os.path.join(directory, filenames[0])
|
||||
|
|
@ -45,12 +44,12 @@ def generate_metadata(
|
|||
Returns the generated metadata directory.
|
||||
"""
|
||||
logger.debug(
|
||||
"Running setup.py (path:%s) egg_info for package %s",
|
||||
'Running setup.py (path:%s) egg_info for package %s',
|
||||
setup_py_path,
|
||||
details,
|
||||
)
|
||||
|
||||
egg_info_dir = TempDirectory(kind="pip-egg-info", globally_managed=True).path
|
||||
egg_info_dir = TempDirectory(kind='pip-egg-info', globally_managed=True).path
|
||||
|
||||
args = make_setuptools_egg_info_args(
|
||||
setup_py_path,
|
||||
|
|
@ -59,12 +58,12 @@ def generate_metadata(
|
|||
)
|
||||
|
||||
with build_env:
|
||||
with open_spinner("Preparing metadata (setup.py)") as spinner:
|
||||
with open_spinner('Preparing metadata (setup.py)') as spinner:
|
||||
try:
|
||||
call_subprocess(
|
||||
args,
|
||||
cwd=source_dir,
|
||||
command_desc="python setup.py egg_info",
|
||||
command_desc='python setup.py egg_info',
|
||||
spinner=spinner,
|
||||
)
|
||||
except InstallationSubprocessError as error:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -14,17 +15,17 @@ def build_wheel_pep517(
|
|||
backend: Pep517HookCaller,
|
||||
metadata_directory: str,
|
||||
tempd: str,
|
||||
) -> Optional[str]:
|
||||
) -> str | None:
|
||||
"""Build one InstallRequirement using the PEP 517 build process.
|
||||
|
||||
Returns path to wheel if successfully built. Otherwise, returns None.
|
||||
"""
|
||||
assert metadata_directory is not None
|
||||
try:
|
||||
logger.debug("Destination directory: %s", tempd)
|
||||
logger.debug('Destination directory: %s', tempd)
|
||||
|
||||
runner = runner_with_spinner_message(
|
||||
f"Building wheel for {name} (pyproject.toml)"
|
||||
f'Building wheel for {name} (pyproject.toml)',
|
||||
)
|
||||
with backend.subprocess_runner(runner):
|
||||
wheel_name = backend.build_wheel(
|
||||
|
|
@ -32,6 +33,6 @@ def build_wheel_pep517(
|
|||
metadata_directory=metadata_directory,
|
||||
)
|
||||
except Exception:
|
||||
logger.error("Failed building wheel for %s", name)
|
||||
logger.error('Failed building wheel for %s', name)
|
||||
return None
|
||||
return os.path.join(tempd, wheel_name)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from pip._vendor.pep517.wrappers import HookMissing, Pep517HookCaller
|
||||
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
from pip._vendor.pep517.wrappers import HookMissing
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -14,17 +16,17 @@ def build_wheel_editable(
|
|||
backend: Pep517HookCaller,
|
||||
metadata_directory: str,
|
||||
tempd: str,
|
||||
) -> Optional[str]:
|
||||
) -> str | None:
|
||||
"""Build one InstallRequirement using the PEP 660 build process.
|
||||
|
||||
Returns path to wheel if successfully built. Otherwise, returns None.
|
||||
"""
|
||||
assert metadata_directory is not None
|
||||
try:
|
||||
logger.debug("Destination directory: %s", tempd)
|
||||
logger.debug('Destination directory: %s', tempd)
|
||||
|
||||
runner = runner_with_spinner_message(
|
||||
f"Building editable for {name} (pyproject.toml)"
|
||||
f'Building editable for {name} (pyproject.toml)',
|
||||
)
|
||||
with backend.subprocess_runner(runner):
|
||||
try:
|
||||
|
|
@ -34,13 +36,13 @@ def build_wheel_editable(
|
|||
)
|
||||
except HookMissing as e:
|
||||
logger.error(
|
||||
"Cannot build editable %s because the build "
|
||||
"backend does not have the %s hook",
|
||||
'Cannot build editable %s because the build '
|
||||
'backend does not have the %s hook',
|
||||
name,
|
||||
e,
|
||||
)
|
||||
return None
|
||||
except Exception:
|
||||
logger.error("Failed building editable for %s", name)
|
||||
logger.error('Failed building editable for %s', name)
|
||||
return None
|
||||
return os.path.join(tempd, wheel_name)
|
||||
|
|
|
|||
|
|
@ -1,54 +1,58 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os.path
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.cli.spinners import open_spinner
|
||||
from pip._internal.utils.setuptools_build import make_setuptools_bdist_wheel_args
|
||||
from pip._internal.utils.subprocess import call_subprocess, format_command_args
|
||||
from pip._internal.utils.subprocess import call_subprocess
|
||||
from pip._internal.utils.subprocess import format_command_args
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def format_command_result(
|
||||
command_args: List[str],
|
||||
command_args: list[str],
|
||||
command_output: str,
|
||||
) -> str:
|
||||
"""Format command information for logging."""
|
||||
command_desc = format_command_args(command_args)
|
||||
text = f"Command arguments: {command_desc}\n"
|
||||
text = f'Command arguments: {command_desc}\n'
|
||||
|
||||
if not command_output:
|
||||
text += "Command output: None"
|
||||
text += 'Command output: None'
|
||||
elif logger.getEffectiveLevel() > logging.DEBUG:
|
||||
text += "Command output: [use --verbose to show]"
|
||||
text += 'Command output: [use --verbose to show]'
|
||||
else:
|
||||
if not command_output.endswith("\n"):
|
||||
command_output += "\n"
|
||||
text += f"Command output:\n{command_output}"
|
||||
if not command_output.endswith('\n'):
|
||||
command_output += '\n'
|
||||
text += f'Command output:\n{command_output}'
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def get_legacy_build_wheel_path(
|
||||
names: List[str],
|
||||
names: list[str],
|
||||
temp_dir: str,
|
||||
name: str,
|
||||
command_args: List[str],
|
||||
command_args: list[str],
|
||||
command_output: str,
|
||||
) -> Optional[str]:
|
||||
) -> str | None:
|
||||
"""Return the path to the wheel in the temporary build directory."""
|
||||
# Sort for determinism.
|
||||
names = sorted(names)
|
||||
if not names:
|
||||
msg = ("Legacy build of wheel for {!r} created no files.\n").format(name)
|
||||
msg = ('Legacy build of wheel for {!r} created no files.\n').format(name)
|
||||
msg += format_command_result(command_args, command_output)
|
||||
logger.warning(msg)
|
||||
return None
|
||||
|
||||
if len(names) > 1:
|
||||
msg = (
|
||||
"Legacy build of wheel for {!r} created more than one file.\n"
|
||||
"Filenames (choosing first): {}\n"
|
||||
'Legacy build of wheel for {!r} created more than one file.\n'
|
||||
'Filenames (choosing first): {}\n'
|
||||
).format(name, names)
|
||||
msg += format_command_result(command_args, command_output)
|
||||
logger.warning(msg)
|
||||
|
|
@ -60,10 +64,10 @@ def build_wheel_legacy(
|
|||
name: str,
|
||||
setup_py_path: str,
|
||||
source_dir: str,
|
||||
global_options: List[str],
|
||||
build_options: List[str],
|
||||
global_options: list[str],
|
||||
build_options: list[str],
|
||||
tempd: str,
|
||||
) -> Optional[str]:
|
||||
) -> str | None:
|
||||
"""Build one unpacked package using the "legacy" build process.
|
||||
|
||||
Returns path to wheel if successfully built. Otherwise, returns None.
|
||||
|
|
@ -75,20 +79,20 @@ def build_wheel_legacy(
|
|||
destination_dir=tempd,
|
||||
)
|
||||
|
||||
spin_message = f"Building wheel for {name} (setup.py)"
|
||||
spin_message = f'Building wheel for {name} (setup.py)'
|
||||
with open_spinner(spin_message) as spinner:
|
||||
logger.debug("Destination directory: %s", tempd)
|
||||
logger.debug('Destination directory: %s', tempd)
|
||||
|
||||
try:
|
||||
output = call_subprocess(
|
||||
wheel_args,
|
||||
command_desc="python setup.py bdist_wheel",
|
||||
command_desc='python setup.py bdist_wheel',
|
||||
cwd=source_dir,
|
||||
spinner=spinner,
|
||||
)
|
||||
except Exception:
|
||||
spinner.finish("error")
|
||||
logger.error("Failed building wheel for %s", name)
|
||||
spinner.finish('error')
|
||||
logger.error('Failed building wheel for %s', name)
|
||||
return None
|
||||
|
||||
names = os.listdir(tempd)
|
||||
|
|
|
|||
|
|
@ -1,23 +1,30 @@
|
|||
"""Validation of dependencies of packages
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Callable, Dict, List, NamedTuple, Optional, Set, Tuple
|
||||
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.distributions import make_distribution_for_install_requirement
|
||||
from pip._internal.metadata import get_default_environment
|
||||
from pip._internal.metadata.base import DistributionVersion
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PackageDetails(NamedTuple):
|
||||
version: DistributionVersion
|
||||
dependencies: List[Requirement]
|
||||
dependencies: list[Requirement]
|
||||
|
||||
|
||||
# Shorthands
|
||||
|
|
@ -31,7 +38,7 @@ CheckResult = Tuple[MissingDict, ConflictingDict]
|
|||
ConflictDetails = Tuple[PackageSet, CheckResult]
|
||||
|
||||
|
||||
def create_package_set_from_installed() -> Tuple[PackageSet, bool]:
|
||||
def create_package_set_from_installed() -> tuple[PackageSet, bool]:
|
||||
"""Converts a list of distributions into a PackageSet."""
|
||||
package_set = {}
|
||||
problems = False
|
||||
|
|
@ -43,13 +50,13 @@ def create_package_set_from_installed() -> Tuple[PackageSet, bool]:
|
|||
package_set[name] = PackageDetails(dist.version, dependencies)
|
||||
except (OSError, ValueError) as e:
|
||||
# Don't crash on unreadable or broken metadata.
|
||||
logger.warning("Error parsing requirements for %s: %s", name, e)
|
||||
logger.warning('Error parsing requirements for %s: %s', name, e)
|
||||
problems = True
|
||||
return package_set, problems
|
||||
|
||||
|
||||
def check_package_set(
|
||||
package_set: PackageSet, should_ignore: Optional[Callable[[str], bool]] = None
|
||||
package_set: PackageSet, should_ignore: Callable[[str], bool] | None = None,
|
||||
) -> CheckResult:
|
||||
"""Check if a package set is consistent
|
||||
|
||||
|
|
@ -62,8 +69,8 @@ def check_package_set(
|
|||
|
||||
for package_name, package_detail in package_set.items():
|
||||
# Info about dependencies of package_name
|
||||
missing_deps: Set[Missing] = set()
|
||||
conflicting_deps: Set[Conflicting] = set()
|
||||
missing_deps: set[Missing] = set()
|
||||
conflicting_deps: set[Conflicting] = set()
|
||||
|
||||
if should_ignore and should_ignore(package_name):
|
||||
continue
|
||||
|
|
@ -93,7 +100,7 @@ def check_package_set(
|
|||
return missing, conflicting
|
||||
|
||||
|
||||
def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDetails:
|
||||
def check_install_conflicts(to_install: list[InstallRequirement]) -> ConflictDetails:
|
||||
"""For checking if the dependency graph would be consistent after \
|
||||
installing given requirements
|
||||
"""
|
||||
|
|
@ -108,14 +115,14 @@ def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDet
|
|||
return (
|
||||
package_set,
|
||||
check_package_set(
|
||||
package_set, should_ignore=lambda name: name not in whitelist
|
||||
package_set, should_ignore=lambda name: name not in whitelist,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _simulate_installation_of(
|
||||
to_install: List[InstallRequirement], package_set: PackageSet
|
||||
) -> Set[NormalizedName]:
|
||||
to_install: list[InstallRequirement], package_set: PackageSet,
|
||||
) -> set[NormalizedName]:
|
||||
"""Computes the version of packages after installing to_install."""
|
||||
# Keep track of packages that were installed
|
||||
installed = set()
|
||||
|
|
@ -133,8 +140,8 @@ def _simulate_installation_of(
|
|||
|
||||
|
||||
def _create_whitelist(
|
||||
would_be_installed: Set[NormalizedName], package_set: PackageSet
|
||||
) -> Set[NormalizedName]:
|
||||
would_be_installed: set[NormalizedName], package_set: PackageSet,
|
||||
) -> set[NormalizedName]:
|
||||
packages_affected = set(would_be_installed)
|
||||
|
||||
for package_name in package_set:
|
||||
|
|
|
|||
|
|
@ -1,38 +1,46 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import logging
|
||||
import os
|
||||
from typing import Container, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set
|
||||
from typing import Container
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
from pip._internal.exceptions import BadCommand, InstallationError
|
||||
from pip._internal.metadata import BaseDistribution, get_environment
|
||||
from pip._internal.req.constructors import (
|
||||
install_req_from_editable,
|
||||
install_req_from_line,
|
||||
)
|
||||
from pip._internal.exceptions import BadCommand
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.metadata import get_environment
|
||||
from pip._internal.req.constructors import install_req_from_editable
|
||||
from pip._internal.req.constructors import install_req_from_line
|
||||
from pip._internal.req.req_file import COMMENT_RE
|
||||
from pip._internal.utils.direct_url_helpers import direct_url_as_pep440_direct_reference
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _EditableInfo(NamedTuple):
|
||||
requirement: str
|
||||
comments: List[str]
|
||||
comments: list[str]
|
||||
|
||||
|
||||
def freeze(
|
||||
requirement: Optional[List[str]] = None,
|
||||
requirement: list[str] | None = None,
|
||||
local_only: bool = False,
|
||||
user_only: bool = False,
|
||||
paths: Optional[List[str]] = None,
|
||||
paths: list[str] | None = None,
|
||||
isolated: bool = False,
|
||||
exclude_editable: bool = False,
|
||||
skip: Container[str] = (),
|
||||
) -> Iterator[str]:
|
||||
installations: Dict[str, FrozenRequirement] = {}
|
||||
installations: dict[str, FrozenRequirement] = {}
|
||||
|
||||
dists = get_environment(paths).iter_installed_distributions(
|
||||
local_only=local_only,
|
||||
|
|
@ -50,30 +58,30 @@ def freeze(
|
|||
# should only be emitted once, even if the same option is in multiple
|
||||
# requirements files, so we need to keep track of what has been emitted
|
||||
# so that we don't emit it again if it's seen again
|
||||
emitted_options: Set[str] = set()
|
||||
emitted_options: set[str] = set()
|
||||
# keep track of which files a requirement is in so that we can
|
||||
# give an accurate warning if a requirement appears multiple times.
|
||||
req_files: Dict[str, List[str]] = collections.defaultdict(list)
|
||||
req_files: dict[str, list[str]] = collections.defaultdict(list)
|
||||
for req_file_path in requirement:
|
||||
with open(req_file_path) as req_file:
|
||||
for line in req_file:
|
||||
if (
|
||||
not line.strip()
|
||||
or line.strip().startswith("#")
|
||||
or line.startswith(
|
||||
not line.strip() or
|
||||
line.strip().startswith('#') or
|
||||
line.startswith(
|
||||
(
|
||||
"-r",
|
||||
"--requirement",
|
||||
"-f",
|
||||
"--find-links",
|
||||
"-i",
|
||||
"--index-url",
|
||||
"--pre",
|
||||
"--trusted-host",
|
||||
"--process-dependency-links",
|
||||
"--extra-index-url",
|
||||
"--use-feature",
|
||||
)
|
||||
'-r',
|
||||
'--requirement',
|
||||
'-f',
|
||||
'--find-links',
|
||||
'-i',
|
||||
'--index-url',
|
||||
'--pre',
|
||||
'--trusted-host',
|
||||
'--process-dependency-links',
|
||||
'--extra-index-url',
|
||||
'--use-feature',
|
||||
),
|
||||
)
|
||||
):
|
||||
line = line.rstrip()
|
||||
|
|
@ -82,31 +90,31 @@ def freeze(
|
|||
yield line
|
||||
continue
|
||||
|
||||
if line.startswith("-e") or line.startswith("--editable"):
|
||||
if line.startswith("-e"):
|
||||
if line.startswith('-e') or line.startswith('--editable'):
|
||||
if line.startswith('-e'):
|
||||
line = line[2:].strip()
|
||||
else:
|
||||
line = line[len("--editable") :].strip().lstrip("=")
|
||||
line = line[len('--editable'):].strip().lstrip('=')
|
||||
line_req = install_req_from_editable(
|
||||
line,
|
||||
isolated=isolated,
|
||||
)
|
||||
else:
|
||||
line_req = install_req_from_line(
|
||||
COMMENT_RE.sub("", line).strip(),
|
||||
COMMENT_RE.sub('', line).strip(),
|
||||
isolated=isolated,
|
||||
)
|
||||
|
||||
if not line_req.name:
|
||||
logger.info(
|
||||
"Skipping line in requirement file [%s] because "
|
||||
'Skipping line in requirement file [%s] because '
|
||||
"it's not clear what it would install: %s",
|
||||
req_file_path,
|
||||
line.strip(),
|
||||
)
|
||||
logger.info(
|
||||
" (add #egg=PackageName to the URL to avoid"
|
||||
" this warning)"
|
||||
' (add #egg=PackageName to the URL to avoid'
|
||||
' this warning)',
|
||||
)
|
||||
else:
|
||||
line_req_canonical_name = canonicalize_name(line_req.name)
|
||||
|
|
@ -115,10 +123,10 @@ def freeze(
|
|||
# but has been processed already
|
||||
if not req_files[line_req.name]:
|
||||
logger.warning(
|
||||
"Requirement file [%s] contains %s, but "
|
||||
"package %r is not installed",
|
||||
'Requirement file [%s] contains %s, but '
|
||||
'package %r is not installed',
|
||||
req_file_path,
|
||||
COMMENT_RE.sub("", line).strip(),
|
||||
COMMENT_RE.sub('', line).strip(),
|
||||
line_req.name,
|
||||
)
|
||||
else:
|
||||
|
|
@ -133,12 +141,12 @@ def freeze(
|
|||
for name, files in req_files.items():
|
||||
if len(files) > 1:
|
||||
logger.warning(
|
||||
"Requirement %s included multiple times [%s]",
|
||||
'Requirement %s included multiple times [%s]',
|
||||
name,
|
||||
", ".join(sorted(set(files))),
|
||||
', '.join(sorted(set(files))),
|
||||
)
|
||||
|
||||
yield ("## The following requirements were added by pip freeze:")
|
||||
yield ('## The following requirements were added by pip freeze:')
|
||||
for installation in sorted(installations.values(), key=lambda x: x.name.lower()):
|
||||
if installation.canonical_name not in skip:
|
||||
yield str(installation).rstrip()
|
||||
|
|
@ -146,8 +154,8 @@ def freeze(
|
|||
|
||||
def _format_as_name_version(dist: BaseDistribution) -> str:
|
||||
if isinstance(dist.version, Version):
|
||||
return f"{dist.raw_name}=={dist.version}"
|
||||
return f"{dist.raw_name}==={dist.version}"
|
||||
return f'{dist.raw_name}=={dist.version}'
|
||||
return f'{dist.raw_name}==={dist.version}'
|
||||
|
||||
|
||||
def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
|
||||
|
|
@ -172,7 +180,7 @@ def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
|
|||
)
|
||||
return _EditableInfo(
|
||||
requirement=location,
|
||||
comments=[f"# Editable install with no version control ({display})"],
|
||||
comments=[f'# Editable install with no version control ({display})'],
|
||||
)
|
||||
|
||||
vcs_name = type(vcs_backend).__name__
|
||||
|
|
@ -183,36 +191,36 @@ def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
|
|||
display = _format_as_name_version(dist)
|
||||
return _EditableInfo(
|
||||
requirement=location,
|
||||
comments=[f"# Editable {vcs_name} install with no remote ({display})"],
|
||||
comments=[f'# Editable {vcs_name} install with no remote ({display})'],
|
||||
)
|
||||
except RemoteNotValidError as ex:
|
||||
display = _format_as_name_version(dist)
|
||||
return _EditableInfo(
|
||||
requirement=location,
|
||||
comments=[
|
||||
f"# Editable {vcs_name} install ({display}) with either a deleted "
|
||||
f"local remote or invalid URI:",
|
||||
f'# Editable {vcs_name} install ({display}) with either a deleted '
|
||||
f'local remote or invalid URI:',
|
||||
f"# '{ex.url}'",
|
||||
],
|
||||
)
|
||||
except BadCommand:
|
||||
logger.warning(
|
||||
"cannot determine version of editable source in %s "
|
||||
"(%s command not found in path)",
|
||||
'cannot determine version of editable source in %s '
|
||||
'(%s command not found in path)',
|
||||
location,
|
||||
vcs_backend.name,
|
||||
)
|
||||
return _EditableInfo(requirement=location, comments=[])
|
||||
except InstallationError as exc:
|
||||
logger.warning("Error when trying to get requirement for VCS system %s", exc)
|
||||
logger.warning('Error when trying to get requirement for VCS system %s', exc)
|
||||
else:
|
||||
return _EditableInfo(requirement=req, comments=[])
|
||||
|
||||
logger.warning("Could not determine repository location of %s", location)
|
||||
logger.warning('Could not determine repository location of %s', location)
|
||||
|
||||
return _EditableInfo(
|
||||
requirement=location,
|
||||
comments=["## !! Could not determine repository location"],
|
||||
comments=['## !! Could not determine repository location'],
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -231,7 +239,7 @@ class FrozenRequirement:
|
|||
self.comments = comments
|
||||
|
||||
@classmethod
|
||||
def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement":
|
||||
def from_dist(cls, dist: BaseDistribution) -> FrozenRequirement:
|
||||
editable = dist.editable
|
||||
if editable:
|
||||
req, comments = _get_editable_info(dist)
|
||||
|
|
@ -250,5 +258,5 @@ class FrozenRequirement:
|
|||
def __str__(self) -> str:
|
||||
req = self.req
|
||||
if self.editable:
|
||||
req = f"-e {req}"
|
||||
return "\n".join(list(self.comments) + [str(req)]) + "\n"
|
||||
req = f'-e {req}'
|
||||
return '\n'.join(list(self.comments) + [str(req)]) + '\n'
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
"""For modules related to installing packages.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
"""Legacy editable installation process, i.e. `setup.py develop`.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import List, Optional, Sequence
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.utils.logging import indent_log
|
||||
|
|
@ -12,10 +16,10 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def install_editable(
|
||||
install_options: List[str],
|
||||
install_options: list[str],
|
||||
global_options: Sequence[str],
|
||||
prefix: Optional[str],
|
||||
home: Optional[str],
|
||||
prefix: str | None,
|
||||
home: str | None,
|
||||
use_user_site: bool,
|
||||
name: str,
|
||||
setup_py_path: str,
|
||||
|
|
@ -26,7 +30,7 @@ def install_editable(
|
|||
"""Install a package in editable mode. Most arguments are pass-through
|
||||
to setuptools.
|
||||
"""
|
||||
logger.info("Running setup.py develop for %s", name)
|
||||
logger.info('Running setup.py develop for %s', name)
|
||||
|
||||
args = make_setuptools_develop_args(
|
||||
setup_py_path,
|
||||
|
|
@ -42,6 +46,6 @@ def install_editable(
|
|||
with build_env:
|
||||
call_subprocess(
|
||||
args,
|
||||
command_desc="python setup.py develop",
|
||||
command_desc='python setup.py develop',
|
||||
cwd=unpacked_source_directory,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
"""Legacy installation process, i.e. `setup.py install`.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from distutils.util import change_root
|
||||
from typing import List, Optional, Sequence
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
from distutils.util import change_root
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.exceptions import InstallationError, LegacyInstallFailure
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import LegacyInstallFailure
|
||||
from pip._internal.models.scheme import Scheme
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
from pip._internal.utils.setuptools_build import make_setuptools_install_args
|
||||
|
|
@ -18,8 +22,8 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def write_installed_files_from_setuptools_record(
|
||||
record_lines: List[str],
|
||||
root: Optional[str],
|
||||
record_lines: list[str],
|
||||
root: str | None,
|
||||
req_description: str,
|
||||
) -> None:
|
||||
def prepend_root(path: str) -> str:
|
||||
|
|
@ -30,14 +34,14 @@ def write_installed_files_from_setuptools_record(
|
|||
|
||||
for line in record_lines:
|
||||
directory = os.path.dirname(line)
|
||||
if directory.endswith(".egg-info"):
|
||||
if directory.endswith('.egg-info'):
|
||||
egg_info_dir = prepend_root(directory)
|
||||
break
|
||||
else:
|
||||
message = (
|
||||
"{} did not indicate that it installed an "
|
||||
".egg-info directory. Only setup.py projects "
|
||||
"generating .egg-info directories are supported."
|
||||
'{} did not indicate that it installed an '
|
||||
'.egg-info directory. Only setup.py projects '
|
||||
'generating .egg-info directories are supported.'
|
||||
).format(req_description)
|
||||
raise InstallationError(message)
|
||||
|
||||
|
|
@ -49,17 +53,17 @@ def write_installed_files_from_setuptools_record(
|
|||
new_lines.append(os.path.relpath(prepend_root(filename), egg_info_dir))
|
||||
new_lines.sort()
|
||||
ensure_dir(egg_info_dir)
|
||||
inst_files_path = os.path.join(egg_info_dir, "installed-files.txt")
|
||||
with open(inst_files_path, "w") as f:
|
||||
f.write("\n".join(new_lines) + "\n")
|
||||
inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt')
|
||||
with open(inst_files_path, 'w') as f:
|
||||
f.write('\n'.join(new_lines) + '\n')
|
||||
|
||||
|
||||
def install(
|
||||
install_options: List[str],
|
||||
install_options: list[str],
|
||||
global_options: Sequence[str],
|
||||
root: Optional[str],
|
||||
home: Optional[str],
|
||||
prefix: Optional[str],
|
||||
root: str | None,
|
||||
home: str | None,
|
||||
prefix: str | None,
|
||||
use_user_site: bool,
|
||||
pycompile: bool,
|
||||
scheme: Scheme,
|
||||
|
|
@ -73,9 +77,9 @@ def install(
|
|||
|
||||
header_dir = scheme.headers
|
||||
|
||||
with TempDirectory(kind="record") as temp_dir:
|
||||
with TempDirectory(kind='record') as temp_dir:
|
||||
try:
|
||||
record_filename = os.path.join(temp_dir.path, "install-record.txt")
|
||||
record_filename = os.path.join(temp_dir.path, 'install-record.txt')
|
||||
install_args = make_setuptools_install_args(
|
||||
setup_py_path,
|
||||
global_options=global_options,
|
||||
|
|
@ -91,7 +95,7 @@ def install(
|
|||
)
|
||||
|
||||
runner = runner_with_spinner_message(
|
||||
f"Running setup.py install for {req_name}"
|
||||
f'Running setup.py install for {req_name}',
|
||||
)
|
||||
with build_env:
|
||||
runner(
|
||||
|
|
@ -100,7 +104,7 @@ def install(
|
|||
)
|
||||
|
||||
if not os.path.exists(record_filename):
|
||||
logger.debug("Record file %s not found", record_filename)
|
||||
logger.debug('Record file %s not found', record_filename)
|
||||
# Signal to the caller that we didn't install the new package
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Support for installing and building the "wheel" binary package format.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import compileall
|
||||
|
|
@ -14,55 +15,57 @@ import sys
|
|||
import warnings
|
||||
from base64 import urlsafe_b64encode
|
||||
from email.message import Message
|
||||
from itertools import chain, filterfalse, starmap
|
||||
from typing import (
|
||||
IO,
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
BinaryIO,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
NewType,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
from zipfile import ZipFile, ZipInfo
|
||||
|
||||
from pip._vendor.distlib.scripts import ScriptMaker
|
||||
from pip._vendor.distlib.util import get_export_entry
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from itertools import chain
|
||||
from itertools import filterfalse
|
||||
from itertools import starmap
|
||||
from typing import Any
|
||||
from typing import BinaryIO
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import IO
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import NewType
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
from zipfile import ZipFile
|
||||
from zipfile import ZipInfo
|
||||
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.locations import get_major_minor_version
|
||||
from pip._internal.metadata import (
|
||||
BaseDistribution,
|
||||
FilesystemWheel,
|
||||
get_wheel_distribution,
|
||||
)
|
||||
from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, DirectUrl
|
||||
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
|
||||
from pip._internal.utils.filesystem import adjacent_tmp_file, replace
|
||||
from pip._internal.utils.misc import captured_stdout, ensure_dir, hash_file, partition
|
||||
from pip._internal.utils.unpacking import (
|
||||
current_umask,
|
||||
is_within_directory,
|
||||
set_extracted_file_to_default_mode_plus_executable,
|
||||
zip_item_is_executable,
|
||||
)
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.metadata import FilesystemWheel
|
||||
from pip._internal.metadata import get_wheel_distribution
|
||||
from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME
|
||||
from pip._internal.models.direct_url import DirectUrl
|
||||
from pip._internal.models.scheme import Scheme
|
||||
from pip._internal.models.scheme import SCHEME_KEYS
|
||||
from pip._internal.utils.filesystem import adjacent_tmp_file
|
||||
from pip._internal.utils.filesystem import replace
|
||||
from pip._internal.utils.misc import captured_stdout
|
||||
from pip._internal.utils.misc import ensure_dir
|
||||
from pip._internal.utils.misc import hash_file
|
||||
from pip._internal.utils.misc import partition
|
||||
from pip._internal.utils.unpacking import current_umask
|
||||
from pip._internal.utils.unpacking import is_within_directory
|
||||
from pip._internal.utils.unpacking import set_extracted_file_to_default_mode_plus_executable
|
||||
from pip._internal.utils.unpacking import zip_item_is_executable
|
||||
from pip._internal.utils.wheel import parse_wheel
|
||||
from pip._vendor.distlib.scripts import ScriptMaker
|
||||
from pip._vendor.distlib.util import get_export_entry
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
|
||||
class File(Protocol):
|
||||
src_record_path: "RecordPath"
|
||||
src_record_path: RecordPath
|
||||
dest_path: str
|
||||
changed: bool
|
||||
|
||||
|
|
@ -72,22 +75,22 @@ if TYPE_CHECKING:
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
RecordPath = NewType("RecordPath", str)
|
||||
RecordPath = NewType('RecordPath', str)
|
||||
InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]]
|
||||
|
||||
|
||||
def rehash(path: str, blocksize: int = 1 << 20) -> Tuple[str, str]:
|
||||
def rehash(path: str, blocksize: int = 1 << 20) -> tuple[str, str]:
|
||||
"""Return (encoded_digest, length) for path using hashlib.sha256()"""
|
||||
h, length = hash_file(path, blocksize)
|
||||
digest = "sha256=" + urlsafe_b64encode(h.digest()).decode("latin1").rstrip("=")
|
||||
digest = 'sha256=' + urlsafe_b64encode(h.digest()).decode('latin1').rstrip('=')
|
||||
return (digest, str(length))
|
||||
|
||||
|
||||
def csv_io_kwargs(mode: str) -> Dict[str, Any]:
|
||||
def csv_io_kwargs(mode: str) -> dict[str, Any]:
|
||||
"""Return keyword arguments to properly open a CSV file
|
||||
in the given mode.
|
||||
"""
|
||||
return {"mode": mode, "newline": "", "encoding": "utf-8"}
|
||||
return {'mode': mode, 'newline': '', 'encoding': 'utf-8'}
|
||||
|
||||
|
||||
def fix_script(path: str) -> bool:
|
||||
|
|
@ -97,35 +100,35 @@ def fix_script(path: str) -> bool:
|
|||
# XXX RECORD hashes will need to be updated
|
||||
assert os.path.isfile(path)
|
||||
|
||||
with open(path, "rb") as script:
|
||||
with open(path, 'rb') as script:
|
||||
firstline = script.readline()
|
||||
if not firstline.startswith(b"#!python"):
|
||||
if not firstline.startswith(b'#!python'):
|
||||
return False
|
||||
exename = sys.executable.encode(sys.getfilesystemencoding())
|
||||
firstline = b"#!" + exename + os.linesep.encode("ascii")
|
||||
firstline = b'#!' + exename + os.linesep.encode('ascii')
|
||||
rest = script.read()
|
||||
with open(path, "wb") as script:
|
||||
with open(path, 'wb') as script:
|
||||
script.write(firstline)
|
||||
script.write(rest)
|
||||
return True
|
||||
|
||||
|
||||
def wheel_root_is_purelib(metadata: Message) -> bool:
|
||||
return metadata.get("Root-Is-Purelib", "").lower() == "true"
|
||||
return metadata.get('Root-Is-Purelib', '').lower() == 'true'
|
||||
|
||||
|
||||
def get_entrypoints(dist: BaseDistribution) -> Tuple[Dict[str, str], Dict[str, str]]:
|
||||
def get_entrypoints(dist: BaseDistribution) -> tuple[dict[str, str], dict[str, str]]:
|
||||
console_scripts = {}
|
||||
gui_scripts = {}
|
||||
for entry_point in dist.iter_entry_points():
|
||||
if entry_point.group == "console_scripts":
|
||||
if entry_point.group == 'console_scripts':
|
||||
console_scripts[entry_point.name] = entry_point.value
|
||||
elif entry_point.group == "gui_scripts":
|
||||
elif entry_point.group == 'gui_scripts':
|
||||
gui_scripts[entry_point.name] = entry_point.value
|
||||
return console_scripts, gui_scripts
|
||||
|
||||
|
||||
def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
|
||||
def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> str | None:
|
||||
"""Determine if any scripts are not on PATH and format a warning.
|
||||
Returns a warning message if one or more scripts are not on PATH,
|
||||
otherwise None.
|
||||
|
|
@ -134,7 +137,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
|
|||
return None
|
||||
|
||||
# Group scripts by the path they were installed in
|
||||
grouped_by_dir: Dict[str, Set[str]] = collections.defaultdict(set)
|
||||
grouped_by_dir: dict[str, set[str]] = collections.defaultdict(set)
|
||||
for destfile in scripts:
|
||||
parent_dir = os.path.dirname(destfile)
|
||||
script_name = os.path.basename(destfile)
|
||||
|
|
@ -143,12 +146,12 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
|
|||
# We don't want to warn for directories that are on PATH.
|
||||
not_warn_dirs = [
|
||||
os.path.normcase(i).rstrip(os.sep)
|
||||
for i in os.environ.get("PATH", "").split(os.pathsep)
|
||||
for i in os.environ.get('PATH', '').split(os.pathsep)
|
||||
]
|
||||
# If an executable sits with sys.executable, we don't warn for it.
|
||||
# This covers the case of venv invocations without activating the venv.
|
||||
not_warn_dirs.append(os.path.normcase(os.path.dirname(sys.executable)))
|
||||
warn_for: Dict[str, Set[str]] = {
|
||||
warn_for: dict[str, set[str]] = {
|
||||
parent_dir: scripts
|
||||
for parent_dir, scripts in grouped_by_dir.items()
|
||||
if os.path.normcase(parent_dir) not in not_warn_dirs
|
||||
|
|
@ -159,47 +162,47 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]:
|
|||
# Format a message
|
||||
msg_lines = []
|
||||
for parent_dir, dir_scripts in warn_for.items():
|
||||
sorted_scripts: List[str] = sorted(dir_scripts)
|
||||
sorted_scripts: list[str] = sorted(dir_scripts)
|
||||
if len(sorted_scripts) == 1:
|
||||
start_text = "script {} is".format(sorted_scripts[0])
|
||||
start_text = f'script {sorted_scripts[0]} is'
|
||||
else:
|
||||
start_text = "scripts {} are".format(
|
||||
", ".join(sorted_scripts[:-1]) + " and " + sorted_scripts[-1]
|
||||
start_text = 'scripts {} are'.format(
|
||||
', '.join(sorted_scripts[:-1]) + ' and ' + sorted_scripts[-1],
|
||||
)
|
||||
|
||||
msg_lines.append(
|
||||
"The {} installed in '{}' which is not on PATH.".format(
|
||||
start_text, parent_dir
|
||||
)
|
||||
start_text, parent_dir,
|
||||
),
|
||||
)
|
||||
|
||||
last_line_fmt = (
|
||||
"Consider adding {} to PATH or, if you prefer "
|
||||
"to suppress this warning, use --no-warn-script-location."
|
||||
'Consider adding {} to PATH or, if you prefer '
|
||||
'to suppress this warning, use --no-warn-script-location.'
|
||||
)
|
||||
if len(msg_lines) == 1:
|
||||
msg_lines.append(last_line_fmt.format("this directory"))
|
||||
msg_lines.append(last_line_fmt.format('this directory'))
|
||||
else:
|
||||
msg_lines.append(last_line_fmt.format("these directories"))
|
||||
msg_lines.append(last_line_fmt.format('these directories'))
|
||||
|
||||
# Add a note if any directory starts with ~
|
||||
warn_for_tilde = any(
|
||||
i[0] == "~" for i in os.environ.get("PATH", "").split(os.pathsep) if i
|
||||
i[0] == '~' for i in os.environ.get('PATH', '').split(os.pathsep) if i
|
||||
)
|
||||
if warn_for_tilde:
|
||||
tilde_warning_msg = (
|
||||
"NOTE: The current PATH contains path(s) starting with `~`, "
|
||||
"which may not be expanded by all applications."
|
||||
'NOTE: The current PATH contains path(s) starting with `~`, '
|
||||
'which may not be expanded by all applications.'
|
||||
)
|
||||
msg_lines.append(tilde_warning_msg)
|
||||
|
||||
# Returns the formatted multiline message
|
||||
return "\n".join(msg_lines)
|
||||
return '\n'.join(msg_lines)
|
||||
|
||||
|
||||
def _normalized_outrows(
|
||||
outrows: Iterable[InstalledCSVRow],
|
||||
) -> List[Tuple[str, str, str]]:
|
||||
) -> list[tuple[str, str, str]]:
|
||||
"""Normalize the given rows of a RECORD file.
|
||||
|
||||
Items in each row are converted into str. Rows are then sorted to make
|
||||
|
|
@ -227,52 +230,52 @@ def _record_to_fs_path(record_path: RecordPath) -> str:
|
|||
return record_path
|
||||
|
||||
|
||||
def _fs_to_record_path(path: str, relative_to: Optional[str] = None) -> RecordPath:
|
||||
def _fs_to_record_path(path: str, relative_to: str | None = None) -> RecordPath:
|
||||
if relative_to is not None:
|
||||
# On Windows, do not handle relative paths if they belong to different
|
||||
# logical disks
|
||||
if (
|
||||
os.path.splitdrive(path)[0].lower()
|
||||
== os.path.splitdrive(relative_to)[0].lower()
|
||||
os.path.splitdrive(path)[0].lower() ==
|
||||
os.path.splitdrive(relative_to)[0].lower()
|
||||
):
|
||||
path = os.path.relpath(path, relative_to)
|
||||
path = path.replace(os.path.sep, "/")
|
||||
return cast("RecordPath", path)
|
||||
path = path.replace(os.path.sep, '/')
|
||||
return cast('RecordPath', path)
|
||||
|
||||
|
||||
def get_csv_rows_for_installed(
|
||||
old_csv_rows: List[List[str]],
|
||||
installed: Dict[RecordPath, RecordPath],
|
||||
changed: Set[RecordPath],
|
||||
generated: List[str],
|
||||
old_csv_rows: list[list[str]],
|
||||
installed: dict[RecordPath, RecordPath],
|
||||
changed: set[RecordPath],
|
||||
generated: list[str],
|
||||
lib_dir: str,
|
||||
) -> List[InstalledCSVRow]:
|
||||
) -> list[InstalledCSVRow]:
|
||||
"""
|
||||
:param installed: A map from archive RECORD path to installation RECORD
|
||||
path.
|
||||
"""
|
||||
installed_rows: List[InstalledCSVRow] = []
|
||||
installed_rows: list[InstalledCSVRow] = []
|
||||
for row in old_csv_rows:
|
||||
if len(row) > 3:
|
||||
logger.warning("RECORD line has more than three elements: %s", row)
|
||||
old_record_path = cast("RecordPath", row[0])
|
||||
logger.warning('RECORD line has more than three elements: %s', row)
|
||||
old_record_path = cast('RecordPath', row[0])
|
||||
new_record_path = installed.pop(old_record_path, old_record_path)
|
||||
if new_record_path in changed:
|
||||
digest, length = rehash(_record_to_fs_path(new_record_path))
|
||||
else:
|
||||
digest = row[1] if len(row) > 1 else ""
|
||||
length = row[2] if len(row) > 2 else ""
|
||||
digest = row[1] if len(row) > 1 else ''
|
||||
length = row[2] if len(row) > 2 else ''
|
||||
installed_rows.append((new_record_path, digest, length))
|
||||
for f in generated:
|
||||
path = _fs_to_record_path(f, lib_dir)
|
||||
digest, length = rehash(f)
|
||||
installed_rows.append((path, digest, length))
|
||||
for installed_record_path in installed.values():
|
||||
installed_rows.append((installed_record_path, "", ""))
|
||||
installed_rows.append((installed_record_path, '', ''))
|
||||
return installed_rows
|
||||
|
||||
|
||||
def get_console_script_specs(console: Dict[str, str]) -> List[str]:
|
||||
def get_console_script_specs(console: dict[str, str]) -> list[str]:
|
||||
"""
|
||||
Given the mapping from entrypoint name to callable, return the relevant
|
||||
console script specs.
|
||||
|
|
@ -315,47 +318,47 @@ def get_console_script_specs(console: Dict[str, str]) -> List[str]:
|
|||
# DEFAULT
|
||||
# - The default behavior is to install pip, pipX, pipX.Y, easy_install
|
||||
# and easy_install-X.Y.
|
||||
pip_script = console.pop("pip", None)
|
||||
pip_script = console.pop('pip', None)
|
||||
if pip_script:
|
||||
if "ENSUREPIP_OPTIONS" not in os.environ:
|
||||
scripts_to_generate.append("pip = " + pip_script)
|
||||
if 'ENSUREPIP_OPTIONS' not in os.environ:
|
||||
scripts_to_generate.append('pip = ' + pip_script)
|
||||
|
||||
if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall":
|
||||
if os.environ.get('ENSUREPIP_OPTIONS', '') != 'altinstall':
|
||||
scripts_to_generate.append(
|
||||
"pip{} = {}".format(sys.version_info[0], pip_script)
|
||||
f'pip{sys.version_info[0]} = {pip_script}',
|
||||
)
|
||||
|
||||
scripts_to_generate.append(f"pip{get_major_minor_version()} = {pip_script}")
|
||||
scripts_to_generate.append(f'pip{get_major_minor_version()} = {pip_script}')
|
||||
# Delete any other versioned pip entry points
|
||||
pip_ep = [k for k in console if re.match(r"pip(\d(\.\d)?)?$", k)]
|
||||
pip_ep = [k for k in console if re.match(r'pip(\d(\.\d)?)?$', k)]
|
||||
for k in pip_ep:
|
||||
del console[k]
|
||||
easy_install_script = console.pop("easy_install", None)
|
||||
easy_install_script = console.pop('easy_install', None)
|
||||
if easy_install_script:
|
||||
if "ENSUREPIP_OPTIONS" not in os.environ:
|
||||
scripts_to_generate.append("easy_install = " + easy_install_script)
|
||||
if 'ENSUREPIP_OPTIONS' not in os.environ:
|
||||
scripts_to_generate.append('easy_install = ' + easy_install_script)
|
||||
|
||||
scripts_to_generate.append(
|
||||
"easy_install-{} = {}".format(
|
||||
get_major_minor_version(), easy_install_script
|
||||
)
|
||||
'easy_install-{} = {}'.format(
|
||||
get_major_minor_version(), easy_install_script,
|
||||
),
|
||||
)
|
||||
# Delete any other versioned easy_install entry points
|
||||
easy_install_ep = [
|
||||
k for k in console if re.match(r"easy_install(-\d\.\d)?$", k)
|
||||
k for k in console if re.match(r'easy_install(-\d\.\d)?$', k)
|
||||
]
|
||||
for k in easy_install_ep:
|
||||
del console[k]
|
||||
|
||||
# Generate the console entry points specified in the wheel
|
||||
scripts_to_generate.extend(starmap("{} = {}".format, console.items()))
|
||||
scripts_to_generate.extend(starmap('{} = {}'.format, console.items()))
|
||||
|
||||
return scripts_to_generate
|
||||
|
||||
|
||||
class ZipBackedFile:
|
||||
def __init__(
|
||||
self, src_record_path: RecordPath, dest_path: str, zip_file: ZipFile
|
||||
self, src_record_path: RecordPath, dest_path: str, zip_file: ZipFile,
|
||||
) -> None:
|
||||
self.src_record_path = src_record_path
|
||||
self.dest_path = dest_path
|
||||
|
|
@ -386,7 +389,7 @@ class ZipBackedFile:
|
|||
zipinfo = self._getinfo()
|
||||
|
||||
with self._zip_file.open(zipinfo) as f:
|
||||
with open(self.dest_path, "wb") as dest:
|
||||
with open(self.dest_path, 'wb') as dest:
|
||||
shutil.copyfileobj(f, dest)
|
||||
|
||||
if zip_item_is_executable(zipinfo):
|
||||
|
|
@ -394,7 +397,7 @@ class ZipBackedFile:
|
|||
|
||||
|
||||
class ScriptFile:
|
||||
def __init__(self, file: "File") -> None:
|
||||
def __init__(self, file: File) -> None:
|
||||
self._file = file
|
||||
self.src_record_path = self._file.src_record_path
|
||||
self.dest_path = self._file.dest_path
|
||||
|
|
@ -408,10 +411,10 @@ class ScriptFile:
|
|||
class MissingCallableSuffix(InstallationError):
|
||||
def __init__(self, entry_point: str) -> None:
|
||||
super().__init__(
|
||||
"Invalid script entry point: {} - A callable "
|
||||
"suffix is required. Cf https://packaging.python.org/"
|
||||
"specifications/entry-points/#use-for-scripts for more "
|
||||
"information.".format(entry_point)
|
||||
'Invalid script entry point: {} - A callable '
|
||||
'suffix is required. Cf https://packaging.python.org/'
|
||||
'specifications/entry-points/#use-for-scripts for more '
|
||||
'information.'.format(entry_point),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -422,7 +425,7 @@ def _raise_for_invalid_entrypoint(specification: str) -> None:
|
|||
|
||||
|
||||
class PipScriptMaker(ScriptMaker):
|
||||
def make(self, specification: str, options: Dict[str, Any] = None) -> List[str]:
|
||||
def make(self, specification: str, options: dict[str, Any] = None) -> list[str]:
|
||||
_raise_for_invalid_entrypoint(specification)
|
||||
return super().make(specification, options)
|
||||
|
||||
|
|
@ -434,7 +437,7 @@ def _install_wheel(
|
|||
scheme: Scheme,
|
||||
pycompile: bool = True,
|
||||
warn_script_location: bool = True,
|
||||
direct_url: Optional[DirectUrl] = None,
|
||||
direct_url: DirectUrl | None = None,
|
||||
requested: bool = False,
|
||||
) -> None:
|
||||
"""Install a wheel.
|
||||
|
|
@ -463,12 +466,12 @@ def _install_wheel(
|
|||
# installed = files copied from the wheel to the destination
|
||||
# changed = files changed while installing (scripts #! line typically)
|
||||
# generated = files newly generated during the install (script wrappers)
|
||||
installed: Dict[RecordPath, RecordPath] = {}
|
||||
changed: Set[RecordPath] = set()
|
||||
generated: List[str] = []
|
||||
installed: dict[RecordPath, RecordPath] = {}
|
||||
changed: set[RecordPath] = set()
|
||||
generated: list[str] = []
|
||||
|
||||
def record_installed(
|
||||
srcfile: RecordPath, destfile: str, modified: bool = False
|
||||
srcfile: RecordPath, destfile: str, modified: bool = False,
|
||||
) -> None:
|
||||
"""Map archive RECORD paths to installation RECORD paths."""
|
||||
newpath = _fs_to_record_path(destfile, lib_dir)
|
||||
|
|
@ -477,22 +480,22 @@ def _install_wheel(
|
|||
changed.add(_fs_to_record_path(destfile))
|
||||
|
||||
def is_dir_path(path: RecordPath) -> bool:
|
||||
return path.endswith("/")
|
||||
return path.endswith('/')
|
||||
|
||||
def assert_no_path_traversal(dest_dir_path: str, target_path: str) -> None:
|
||||
if not is_within_directory(dest_dir_path, target_path):
|
||||
message = (
|
||||
"The wheel {!r} has a file {!r} trying to install"
|
||||
" outside the target directory {!r}"
|
||||
'The wheel {!r} has a file {!r} trying to install'
|
||||
' outside the target directory {!r}'
|
||||
)
|
||||
raise InstallationError(
|
||||
message.format(wheel_path, target_path, dest_dir_path)
|
||||
message.format(wheel_path, target_path, dest_dir_path),
|
||||
)
|
||||
|
||||
def root_scheme_file_maker(
|
||||
zip_file: ZipFile, dest: str
|
||||
) -> Callable[[RecordPath], "File"]:
|
||||
def make_root_scheme_file(record_path: RecordPath) -> "File":
|
||||
zip_file: ZipFile, dest: str,
|
||||
) -> Callable[[RecordPath], File]:
|
||||
def make_root_scheme_file(record_path: RecordPath) -> File:
|
||||
normed_path = os.path.normpath(record_path)
|
||||
dest_path = os.path.join(dest, normed_path)
|
||||
assert_no_path_traversal(dest, dest_path)
|
||||
|
|
@ -501,17 +504,17 @@ def _install_wheel(
|
|||
return make_root_scheme_file
|
||||
|
||||
def data_scheme_file_maker(
|
||||
zip_file: ZipFile, scheme: Scheme
|
||||
) -> Callable[[RecordPath], "File"]:
|
||||
zip_file: ZipFile, scheme: Scheme,
|
||||
) -> Callable[[RecordPath], File]:
|
||||
scheme_paths = {key: getattr(scheme, key) for key in SCHEME_KEYS}
|
||||
|
||||
def make_data_scheme_file(record_path: RecordPath) -> "File":
|
||||
def make_data_scheme_file(record_path: RecordPath) -> File:
|
||||
normed_path = os.path.normpath(record_path)
|
||||
try:
|
||||
_, scheme_key, dest_subpath = normed_path.split(os.path.sep, 2)
|
||||
except ValueError:
|
||||
message = (
|
||||
"Unexpected file in {}: {!r}. .data directory contents"
|
||||
'Unexpected file in {}: {!r}. .data directory contents'
|
||||
" should be named like: '<scheme key>/<path>'."
|
||||
).format(wheel_path, record_path)
|
||||
raise InstallationError(message)
|
||||
|
|
@ -519,11 +522,11 @@ def _install_wheel(
|
|||
try:
|
||||
scheme_path = scheme_paths[scheme_key]
|
||||
except KeyError:
|
||||
valid_scheme_keys = ", ".join(sorted(scheme_paths))
|
||||
valid_scheme_keys = ', '.join(sorted(scheme_paths))
|
||||
message = (
|
||||
"Unknown scheme key used in {}: {} (for file {!r}). .data"
|
||||
" directory contents should be in subdirectories named"
|
||||
" with a valid scheme key ({})"
|
||||
'Unknown scheme key used in {}: {} (for file {!r}). .data'
|
||||
' directory contents should be in subdirectories named'
|
||||
' with a valid scheme key ({})'
|
||||
).format(wheel_path, scheme_key, record_path, valid_scheme_keys)
|
||||
raise InstallationError(message)
|
||||
|
||||
|
|
@ -534,7 +537,7 @@ def _install_wheel(
|
|||
return make_data_scheme_file
|
||||
|
||||
def is_data_scheme_path(path: RecordPath) -> bool:
|
||||
return path.split("/", 1)[0].endswith(".data")
|
||||
return path.split('/', 1)[0].endswith('.data')
|
||||
|
||||
paths = cast(List[RecordPath], wheel_zip.namelist())
|
||||
file_paths = filterfalse(is_dir_path, paths)
|
||||
|
|
@ -544,11 +547,11 @@ def _install_wheel(
|
|||
files: Iterator[File] = map(make_root_scheme_file, root_scheme_paths)
|
||||
|
||||
def is_script_scheme_path(path: RecordPath) -> bool:
|
||||
parts = path.split("/", 2)
|
||||
return len(parts) > 2 and parts[0].endswith(".data") and parts[1] == "scripts"
|
||||
parts = path.split('/', 2)
|
||||
return len(parts) > 2 and parts[0].endswith('.data') and parts[1] == 'scripts'
|
||||
|
||||
other_scheme_paths, script_scheme_paths = partition(
|
||||
is_script_scheme_path, data_scheme_paths
|
||||
is_script_scheme_path, data_scheme_paths,
|
||||
)
|
||||
|
||||
make_data_scheme_file = data_scheme_file_maker(wheel_zip, scheme)
|
||||
|
|
@ -562,16 +565,16 @@ def _install_wheel(
|
|||
)
|
||||
console, gui = get_entrypoints(distribution)
|
||||
|
||||
def is_entrypoint_wrapper(file: "File") -> bool:
|
||||
def is_entrypoint_wrapper(file: File) -> bool:
|
||||
# EP, EP.exe and EP-script.py are scripts generated for
|
||||
# entry point EP by setuptools
|
||||
path = file.dest_path
|
||||
name = os.path.basename(path)
|
||||
if name.lower().endswith(".exe"):
|
||||
if name.lower().endswith('.exe'):
|
||||
matchname = name[:-4]
|
||||
elif name.lower().endswith("-script.py"):
|
||||
elif name.lower().endswith('-script.py'):
|
||||
matchname = name[:-10]
|
||||
elif name.lower().endswith(".pya"):
|
||||
elif name.lower().endswith('.pya'):
|
||||
matchname = name[:-4]
|
||||
else:
|
||||
matchname = name
|
||||
|
|
@ -579,7 +582,7 @@ def _install_wheel(
|
|||
return matchname in console or matchname in gui
|
||||
|
||||
script_scheme_files: Iterator[File] = map(
|
||||
make_data_scheme_file, script_scheme_paths
|
||||
make_data_scheme_file, script_scheme_paths,
|
||||
)
|
||||
script_scheme_files = filterfalse(is_entrypoint_wrapper, script_scheme_files)
|
||||
script_scheme_files = map(ScriptFile, script_scheme_files)
|
||||
|
|
@ -598,7 +601,7 @@ def _install_wheel(
|
|||
full_installed_path = os.path.join(lib_dir, installed_path)
|
||||
if not os.path.isfile(full_installed_path):
|
||||
continue
|
||||
if not full_installed_path.endswith(".py"):
|
||||
if not full_installed_path.endswith('.py'):
|
||||
continue
|
||||
yield full_installed_path
|
||||
|
||||
|
|
@ -610,14 +613,14 @@ def _install_wheel(
|
|||
if pycompile:
|
||||
with captured_stdout() as stdout:
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore")
|
||||
warnings.filterwarnings('ignore')
|
||||
for path in pyc_source_file_paths():
|
||||
success = compileall.compile_file(path, force=True, quiet=True)
|
||||
if success:
|
||||
pyc_path = pyc_output_path(path)
|
||||
assert os.path.exists(pyc_path)
|
||||
pyc_record_path = cast(
|
||||
"RecordPath", pyc_path.replace(os.path.sep, "/")
|
||||
'RecordPath', pyc_path.replace(os.path.sep, '/'),
|
||||
)
|
||||
record_installed(pyc_record_path, pyc_path)
|
||||
logger.debug(stdout.getvalue())
|
||||
|
|
@ -631,7 +634,7 @@ def _install_wheel(
|
|||
# Ensure we don't generate any variants for scripts because this is almost
|
||||
# never what somebody wants.
|
||||
# See https://bitbucket.org/pypa/distlib/issue/35/
|
||||
maker.variants = {""}
|
||||
maker.variants = {''}
|
||||
|
||||
# This is required because otherwise distlib creates scripts that are not
|
||||
# executable.
|
||||
|
|
@ -641,12 +644,12 @@ def _install_wheel(
|
|||
# Generate the console and GUI entry points specified in the wheel
|
||||
scripts_to_generate = get_console_script_specs(console)
|
||||
|
||||
gui_scripts_to_generate = list(starmap("{} = {}".format, gui.items()))
|
||||
gui_scripts_to_generate = list(starmap('{} = {}'.format, gui.items()))
|
||||
|
||||
generated_console_scripts = maker.make_multiple(scripts_to_generate)
|
||||
generated.extend(generated_console_scripts)
|
||||
|
||||
generated.extend(maker.make_multiple(gui_scripts_to_generate, {"gui": True}))
|
||||
generated.extend(maker.make_multiple(gui_scripts_to_generate, {'gui': True}))
|
||||
|
||||
if warn_script_location:
|
||||
msg = message_about_scripts_not_on_PATH(generated_console_scripts)
|
||||
|
|
@ -665,26 +668,26 @@ def _install_wheel(
|
|||
dest_info_dir = os.path.join(lib_dir, info_dir)
|
||||
|
||||
# Record pip as the installer
|
||||
installer_path = os.path.join(dest_info_dir, "INSTALLER")
|
||||
installer_path = os.path.join(dest_info_dir, 'INSTALLER')
|
||||
with _generate_file(installer_path) as installer_file:
|
||||
installer_file.write(b"pip\n")
|
||||
installer_file.write(b'pip\n')
|
||||
generated.append(installer_path)
|
||||
|
||||
# Record the PEP 610 direct URL reference
|
||||
if direct_url is not None:
|
||||
direct_url_path = os.path.join(dest_info_dir, DIRECT_URL_METADATA_NAME)
|
||||
with _generate_file(direct_url_path) as direct_url_file:
|
||||
direct_url_file.write(direct_url.to_json().encode("utf-8"))
|
||||
direct_url_file.write(direct_url.to_json().encode('utf-8'))
|
||||
generated.append(direct_url_path)
|
||||
|
||||
# Record the REQUESTED file
|
||||
if requested:
|
||||
requested_path = os.path.join(dest_info_dir, "REQUESTED")
|
||||
with open(requested_path, "wb"):
|
||||
requested_path = os.path.join(dest_info_dir, 'REQUESTED')
|
||||
with open(requested_path, 'wb'):
|
||||
pass
|
||||
generated.append(requested_path)
|
||||
|
||||
record_text = distribution.read_text("RECORD")
|
||||
record_text = distribution.read_text('RECORD')
|
||||
record_rows = list(csv.reader(record_text.splitlines()))
|
||||
|
||||
rows = get_csv_rows_for_installed(
|
||||
|
|
@ -696,12 +699,12 @@ def _install_wheel(
|
|||
)
|
||||
|
||||
# Record details of all files installed
|
||||
record_path = os.path.join(dest_info_dir, "RECORD")
|
||||
record_path = os.path.join(dest_info_dir, 'RECORD')
|
||||
|
||||
with _generate_file(record_path, **csv_io_kwargs("w")) as record_file:
|
||||
with _generate_file(record_path, **csv_io_kwargs('w')) as record_file:
|
||||
# Explicitly cast to typing.IO[str] as a workaround for the mypy error:
|
||||
# "writer" has incompatible type "BinaryIO"; expected "_Writer"
|
||||
writer = csv.writer(cast("IO[str]", record_file))
|
||||
writer = csv.writer(cast('IO[str]', record_file))
|
||||
writer.writerows(_normalized_outrows(rows))
|
||||
|
||||
|
||||
|
|
@ -710,7 +713,7 @@ def req_error_context(req_description: str) -> Iterator[None]:
|
|||
try:
|
||||
yield
|
||||
except InstallationError as e:
|
||||
message = "For req: {}. {}".format(req_description, e.args[0])
|
||||
message = f'For req: {req_description}. {e.args[0]}'
|
||||
raise InstallationError(message) from e
|
||||
|
||||
|
||||
|
|
@ -721,7 +724,7 @@ def install_wheel(
|
|||
req_description: str,
|
||||
pycompile: bool = True,
|
||||
warn_script_location: bool = True,
|
||||
direct_url: Optional[DirectUrl] = None,
|
||||
direct_url: DirectUrl | None = None,
|
||||
requested: bool = False,
|
||||
) -> None:
|
||||
with ZipFile(wheel_path, allowZip64=True) as z:
|
||||
|
|
|
|||
|
|
@ -1,47 +1,50 @@
|
|||
"""Prepares a distribution for installation
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import shutil
|
||||
from typing import Dict, Iterable, List, Optional
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.distributions import make_distribution_for_install_requirement
|
||||
from pip._internal.distributions.installed import InstalledDistribution
|
||||
from pip._internal.exceptions import (
|
||||
DirectoryUrlHashUnsupported,
|
||||
HashMismatch,
|
||||
HashUnpinned,
|
||||
InstallationError,
|
||||
NetworkConnectionError,
|
||||
PreviousBuildDirError,
|
||||
VcsHashUnsupported,
|
||||
)
|
||||
from pip._internal.exceptions import DirectoryUrlHashUnsupported
|
||||
from pip._internal.exceptions import HashMismatch
|
||||
from pip._internal.exceptions import HashUnpinned
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import NetworkConnectionError
|
||||
from pip._internal.exceptions import PreviousBuildDirError
|
||||
from pip._internal.exceptions import VcsHashUnsupported
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.models.wheel import Wheel
|
||||
from pip._internal.network.download import BatchDownloader, Downloader
|
||||
from pip._internal.network.lazy_wheel import (
|
||||
HTTPRangeRequestUnsupported,
|
||||
dist_from_wheel_url,
|
||||
)
|
||||
from pip._internal.network.download import BatchDownloader
|
||||
from pip._internal.network.download import Downloader
|
||||
from pip._internal.network.lazy_wheel import dist_from_wheel_url
|
||||
from pip._internal.network.lazy_wheel import HTTPRangeRequestUnsupported
|
||||
from pip._internal.network.session import PipSession
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.req.req_tracker import RequirementTracker
|
||||
from pip._internal.utils.filesystem import copy2_fixed
|
||||
from pip._internal.utils.hashes import Hashes, MissingHashes
|
||||
from pip._internal.utils.hashes import Hashes
|
||||
from pip._internal.utils.hashes import MissingHashes
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import display_path, hide_url, is_installable_dir, rmtree
|
||||
from pip._internal.utils.misc import display_path
|
||||
from pip._internal.utils.misc import hide_url
|
||||
from pip._internal.utils.misc import is_installable_dir
|
||||
from pip._internal.utils.misc import rmtree
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._internal.utils.unpacking import unpack_file
|
||||
from pip._internal.vcs import vcs
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -66,7 +69,7 @@ def unpack_vcs_link(link: Link, location: str, verbosity: int) -> None:
|
|||
|
||||
|
||||
class File:
|
||||
def __init__(self, path: str, content_type: Optional[str]) -> None:
|
||||
def __init__(self, path: str, content_type: str | None) -> None:
|
||||
self.path = path
|
||||
if content_type is None:
|
||||
self.content_type = mimetypes.guess_type(path)[0]
|
||||
|
|
@ -77,10 +80,10 @@ class File:
|
|||
def get_http_url(
|
||||
link: Link,
|
||||
download: Downloader,
|
||||
download_dir: Optional[str] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
download_dir: str | None = None,
|
||||
hashes: Hashes | None = None,
|
||||
) -> File:
|
||||
temp_dir = TempDirectory(kind="unpack", globally_managed=True)
|
||||
temp_dir = TempDirectory(kind='unpack', globally_managed=True)
|
||||
# If a download dir is specified, is the file already downloaded there?
|
||||
already_downloaded_path = None
|
||||
if download_dir:
|
||||
|
|
@ -123,14 +126,14 @@ def _copy_source_tree(source: str, target: str) -> None:
|
|||
target_basename = os.path.basename(target_abspath)
|
||||
target_dirname = os.path.dirname(target_abspath)
|
||||
|
||||
def ignore(d: str, names: List[str]) -> List[str]:
|
||||
skipped: List[str] = []
|
||||
def ignore(d: str, names: list[str]) -> list[str]:
|
||||
skipped: list[str] = []
|
||||
if d == source:
|
||||
# Pulling in those directories can potentially be very slow,
|
||||
# exclude the following directories if they appear in the top
|
||||
# level dir (and only it).
|
||||
# See discussion at https://github.com/pypa/pip/pull/6770
|
||||
skipped += [".tox", ".nox"]
|
||||
skipped += ['.tox', '.nox']
|
||||
if os.path.abspath(d) == target_dirname:
|
||||
# Prevent an infinite recursion if the target is in source.
|
||||
# This can happen when TMPDIR is set to ${PWD}/...
|
||||
|
|
@ -148,7 +151,7 @@ def _copy_source_tree(source: str, target: str) -> None:
|
|||
|
||||
|
||||
def get_file_url(
|
||||
link: Link, download_dir: Optional[str] = None, hashes: Optional[Hashes] = None
|
||||
link: Link, download_dir: str | None = None, hashes: Hashes | None = None,
|
||||
) -> File:
|
||||
"""Get file and optionally check its hash."""
|
||||
# If a download dir is specified, is the file already there and valid?
|
||||
|
|
@ -176,9 +179,9 @@ def unpack_url(
|
|||
location: str,
|
||||
download: Downloader,
|
||||
verbosity: int,
|
||||
download_dir: Optional[str] = None,
|
||||
hashes: Optional[Hashes] = None,
|
||||
) -> Optional[File]:
|
||||
download_dir: str | None = None,
|
||||
hashes: Hashes | None = None,
|
||||
) -> File | None:
|
||||
"""Unpack link into location, downloading if required.
|
||||
|
||||
:param hashes: A Hashes object, one of whose embedded hashes must match,
|
||||
|
|
@ -227,8 +230,8 @@ def unpack_url(
|
|||
|
||||
|
||||
def _check_download_dir(
|
||||
link: Link, download_dir: str, hashes: Optional[Hashes]
|
||||
) -> Optional[str]:
|
||||
link: Link, download_dir: str, hashes: Hashes | None,
|
||||
) -> str | None:
|
||||
"""Check download_dir for previously downloaded file with correct hash
|
||||
If a correct file is found return its path else None
|
||||
"""
|
||||
|
|
@ -238,13 +241,13 @@ def _check_download_dir(
|
|||
return None
|
||||
|
||||
# If already downloaded, does its hash match?
|
||||
logger.info("File was already downloaded %s", download_path)
|
||||
logger.info('File was already downloaded %s', download_path)
|
||||
if hashes:
|
||||
try:
|
||||
hashes.check_against_path(download_path)
|
||||
except HashMismatch:
|
||||
logger.warning(
|
||||
"Previously-downloaded file %s has bad hash. Re-downloading.",
|
||||
'Previously-downloaded file %s has bad hash. Re-downloading.',
|
||||
download_path,
|
||||
)
|
||||
os.unlink(download_path)
|
||||
|
|
@ -258,7 +261,7 @@ class RequirementPreparer:
|
|||
def __init__(
|
||||
self,
|
||||
build_dir: str,
|
||||
download_dir: Optional[str],
|
||||
download_dir: str | None,
|
||||
src_dir: str,
|
||||
build_isolation: bool,
|
||||
req_tracker: RequirementTracker,
|
||||
|
|
@ -304,18 +307,18 @@ class RequirementPreparer:
|
|||
self.in_tree_build = in_tree_build
|
||||
|
||||
# Memoized downloaded files, as mapping of url: path.
|
||||
self._downloaded: Dict[str, str] = {}
|
||||
self._downloaded: dict[str, str] = {}
|
||||
|
||||
# Previous "header" printed for a link-based InstallRequirement
|
||||
self._previous_requirement_header = ("", "")
|
||||
self._previous_requirement_header = ('', '')
|
||||
|
||||
def _log_preparing_link(self, req: InstallRequirement) -> None:
|
||||
"""Provide context for the requirement being prepared."""
|
||||
if req.link.is_file and not req.original_link_is_in_wheel_cache:
|
||||
message = "Processing %s"
|
||||
message = 'Processing %s'
|
||||
information = str(display_path(req.link.file_path))
|
||||
else:
|
||||
message = "Collecting %s"
|
||||
message = 'Collecting %s'
|
||||
information = str(req.req or req)
|
||||
|
||||
if (message, information) != self._previous_requirement_header:
|
||||
|
|
@ -324,10 +327,10 @@ class RequirementPreparer:
|
|||
|
||||
if req.original_link_is_in_wheel_cache:
|
||||
with indent_log():
|
||||
logger.info("Using cached %s", req.link.filename)
|
||||
logger.info('Using cached %s', req.link.filename)
|
||||
|
||||
def _ensure_link_req_src_dir(
|
||||
self, req: InstallRequirement, parallel_builds: bool
|
||||
self, req: InstallRequirement, parallel_builds: bool,
|
||||
) -> None:
|
||||
"""Ensure source_dir of a linked InstallRequirement."""
|
||||
# Since source_dir is only set for editable requirements.
|
||||
|
|
@ -357,10 +360,10 @@ class RequirementPreparer:
|
|||
if is_installable_dir(req.source_dir):
|
||||
raise PreviousBuildDirError(
|
||||
"pip can't proceed with requirements '{}' due to a"
|
||||
"pre-existing build directory ({}). 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.".format(req, req.source_dir)
|
||||
'pre-existing build directory ({}). 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.'.format(req, req.source_dir),
|
||||
)
|
||||
|
||||
def _get_linked_req_hashes(self, req: InstallRequirement) -> Hashes:
|
||||
|
|
@ -398,16 +401,16 @@ class RequirementPreparer:
|
|||
def _fetch_metadata_using_lazy_wheel(
|
||||
self,
|
||||
link: Link,
|
||||
) -> Optional[BaseDistribution]:
|
||||
) -> BaseDistribution | None:
|
||||
"""Fetch metadata using lazy wheel, if possible."""
|
||||
if not self.use_lazy_wheel:
|
||||
return None
|
||||
if self.require_hashes:
|
||||
logger.debug("Lazy wheel is not used as hash checking is required")
|
||||
logger.debug('Lazy wheel is not used as hash checking is required')
|
||||
return None
|
||||
if link.is_file or not link.is_wheel:
|
||||
logger.debug(
|
||||
"Lazy wheel is not used as %r does not points to a remote wheel",
|
||||
'Lazy wheel is not used as %r does not points to a remote wheel',
|
||||
link,
|
||||
)
|
||||
return None
|
||||
|
|
@ -415,15 +418,15 @@ class RequirementPreparer:
|
|||
wheel = Wheel(link.filename)
|
||||
name = canonicalize_name(wheel.name)
|
||||
logger.info(
|
||||
"Obtaining dependency information from %s %s",
|
||||
'Obtaining dependency information from %s %s',
|
||||
name,
|
||||
wheel.version,
|
||||
)
|
||||
url = link.url.split("#", 1)[0]
|
||||
url = link.url.split('#', 1)[0]
|
||||
try:
|
||||
return dist_from_wheel_url(name, url, self._session)
|
||||
except HTTPRangeRequestUnsupported:
|
||||
logger.debug("%s does not support range requests", url)
|
||||
logger.debug('%s does not support range requests', url)
|
||||
return None
|
||||
|
||||
def _complete_partial_requirements(
|
||||
|
|
@ -434,12 +437,12 @@ class RequirementPreparer:
|
|||
"""Download any requirements which were only fetched by metadata."""
|
||||
# Download to a temporary directory. These will be copied over as
|
||||
# needed for downstream 'download', 'wheel', and 'install' commands.
|
||||
temp_dir = TempDirectory(kind="unpack", globally_managed=True).path
|
||||
temp_dir = TempDirectory(kind='unpack', globally_managed=True).path
|
||||
|
||||
# Map each link to the requirement that owns it. This allows us to set
|
||||
# `req.local_file_path` on the appropriate requirement after passing
|
||||
# all the links at once into BatchDownloader.
|
||||
links_to_fully_download: Dict[Link, InstallRequirement] = {}
|
||||
links_to_fully_download: dict[Link, InstallRequirement] = {}
|
||||
for req in partially_downloaded_reqs:
|
||||
assert req.link
|
||||
links_to_fully_download[req.link] = req
|
||||
|
|
@ -449,7 +452,7 @@ class RequirementPreparer:
|
|||
temp_dir,
|
||||
)
|
||||
for link, (filepath, _) in batch_download:
|
||||
logger.debug("Downloading link %s to %s", link, filepath)
|
||||
logger.debug('Downloading link %s to %s', link, filepath)
|
||||
req = links_to_fully_download[link]
|
||||
req.local_file_path = filepath
|
||||
|
||||
|
|
@ -459,7 +462,7 @@ class RequirementPreparer:
|
|||
self._prepare_linked_requirement(req, parallel_builds)
|
||||
|
||||
def prepare_linked_requirement(
|
||||
self, req: InstallRequirement, parallel_builds: bool = False
|
||||
self, req: InstallRequirement, parallel_builds: bool = False,
|
||||
) -> BaseDistribution:
|
||||
"""Prepare a requirement to be obtained from req.link."""
|
||||
assert req.link
|
||||
|
|
@ -487,7 +490,7 @@ class RequirementPreparer:
|
|||
return self._prepare_linked_requirement(req, parallel_builds)
|
||||
|
||||
def prepare_linked_requirements_more(
|
||||
self, reqs: Iterable[InstallRequirement], parallel_builds: bool = False
|
||||
self, reqs: Iterable[InstallRequirement], parallel_builds: bool = False,
|
||||
) -> None:
|
||||
"""Prepare linked requirements more, if needed."""
|
||||
reqs = [req for req in reqs if req.needs_more_preparation]
|
||||
|
|
@ -502,7 +505,7 @@ class RequirementPreparer:
|
|||
|
||||
# Prepare requirements we found were already downloaded for some
|
||||
# reason. The other downloads will be completed separately.
|
||||
partially_downloaded_reqs: List[InstallRequirement] = []
|
||||
partially_downloaded_reqs: list[InstallRequirement] = []
|
||||
for req in reqs:
|
||||
if req.needs_more_preparation:
|
||||
partially_downloaded_reqs.append(req)
|
||||
|
|
@ -517,7 +520,7 @@ class RequirementPreparer:
|
|||
)
|
||||
|
||||
def _prepare_linked_requirement(
|
||||
self, req: InstallRequirement, parallel_builds: bool
|
||||
self, req: InstallRequirement, parallel_builds: bool,
|
||||
) -> BaseDistribution:
|
||||
assert req.link
|
||||
link = req.link
|
||||
|
|
@ -539,8 +542,8 @@ class RequirementPreparer:
|
|||
)
|
||||
except NetworkConnectionError as exc:
|
||||
raise InstallationError(
|
||||
"Could not install requirement {} because of HTTP "
|
||||
"error {} for URL {}".format(req, exc, link)
|
||||
'Could not install requirement {} because of HTTP '
|
||||
'error {} for URL {}'.format(req, exc, link),
|
||||
)
|
||||
else:
|
||||
file_path = self._downloaded[link.url]
|
||||
|
|
@ -572,8 +575,8 @@ class RequirementPreparer:
|
|||
|
||||
if link.is_existing_dir():
|
||||
logger.debug(
|
||||
"Not copying link to destination directory "
|
||||
"since it is a directory: %s",
|
||||
'Not copying link to destination directory '
|
||||
'since it is a directory: %s',
|
||||
link,
|
||||
)
|
||||
return
|
||||
|
|
@ -585,23 +588,23 @@ class RequirementPreparer:
|
|||
if not os.path.exists(download_location):
|
||||
shutil.copy(req.local_file_path, download_location)
|
||||
download_path = display_path(download_location)
|
||||
logger.info("Saved %s", download_path)
|
||||
logger.info('Saved %s', download_path)
|
||||
|
||||
def prepare_editable_requirement(
|
||||
self,
|
||||
req: InstallRequirement,
|
||||
) -> BaseDistribution:
|
||||
"""Prepare an editable requirement."""
|
||||
assert req.editable, "cannot prepare a non-editable req as editable"
|
||||
assert req.editable, 'cannot prepare a non-editable req as editable'
|
||||
|
||||
logger.info("Obtaining %s", req)
|
||||
logger.info('Obtaining %s', req)
|
||||
|
||||
with indent_log():
|
||||
if self.require_hashes:
|
||||
raise InstallationError(
|
||||
"The editable requirement {} cannot be installed when "
|
||||
"requiring hashes, because there is no single file to "
|
||||
"hash.".format(req)
|
||||
'The editable requirement {} cannot be installed when '
|
||||
'requiring hashes, because there is no single file to '
|
||||
'hash.'.format(req),
|
||||
)
|
||||
req.ensure_has_source_dir(self.src_dir)
|
||||
req.update_editable()
|
||||
|
|
@ -625,18 +628,18 @@ class RequirementPreparer:
|
|||
"""Prepare an already-installed requirement."""
|
||||
assert req.satisfied_by, "req should have been satisfied but isn't"
|
||||
assert skip_reason is not None, (
|
||||
"did not get skip reason skipped but req.satisfied_by "
|
||||
"is set to {}".format(req.satisfied_by)
|
||||
'did not get skip reason skipped but req.satisfied_by '
|
||||
'is set to {}'.format(req.satisfied_by)
|
||||
)
|
||||
logger.info(
|
||||
"Requirement %s: %s (%s)", skip_reason, req, req.satisfied_by.version
|
||||
'Requirement %s: %s (%s)', skip_reason, req, req.satisfied_by.version,
|
||||
)
|
||||
with indent_log():
|
||||
if self.require_hashes:
|
||||
logger.debug(
|
||||
"Since it is already installed, we are trusting this "
|
||||
"package without checking its hash. To ensure a "
|
||||
"completely repeatable environment, install into an "
|
||||
"empty virtualenv."
|
||||
'Since it is already installed, we are trusting this '
|
||||
'package without checking its hash. To ensure a '
|
||||
'completely repeatable environment, install into an '
|
||||
'empty virtualenv.',
|
||||
)
|
||||
return InstalledDistribution(req).get_metadata_distribution()
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections import namedtuple
|
||||
from typing import Any, List, Optional
|
||||
from typing import Any
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import InvalidPyProjectBuildRequires
|
||||
from pip._internal.exceptions import MissingPyProjectBuildRequires
|
||||
from pip._vendor import tomli
|
||||
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
|
||||
|
||||
from pip._internal.exceptions import (
|
||||
InstallationError,
|
||||
InvalidPyProjectBuildRequires,
|
||||
MissingPyProjectBuildRequires,
|
||||
)
|
||||
from pip._vendor.packaging.requirements import InvalidRequirement
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
|
||||
|
||||
def _is_list_of_str(obj: Any) -> bool:
|
||||
|
|
@ -17,17 +19,17 @@ def _is_list_of_str(obj: Any) -> bool:
|
|||
|
||||
|
||||
def make_pyproject_path(unpacked_source_directory: str) -> str:
|
||||
return os.path.join(unpacked_source_directory, "pyproject.toml")
|
||||
return os.path.join(unpacked_source_directory, 'pyproject.toml')
|
||||
|
||||
|
||||
BuildSystemDetails = namedtuple(
|
||||
"BuildSystemDetails", ["requires", "backend", "check", "backend_path"]
|
||||
'BuildSystemDetails', ['requires', 'backend', 'check', 'backend_path'],
|
||||
)
|
||||
|
||||
|
||||
def load_pyproject_toml(
|
||||
use_pep517: Optional[bool], pyproject_toml: str, setup_py: str, req_name: str
|
||||
) -> Optional[BuildSystemDetails]:
|
||||
use_pep517: bool | None, pyproject_toml: str, setup_py: str, req_name: str,
|
||||
) -> BuildSystemDetails | None:
|
||||
"""Load the pyproject.toml file.
|
||||
|
||||
Parameters:
|
||||
|
|
@ -54,14 +56,14 @@ def load_pyproject_toml(
|
|||
|
||||
if not has_pyproject and not has_setup:
|
||||
raise InstallationError(
|
||||
f"{req_name} does not appear to be a Python project: "
|
||||
f"neither 'setup.py' nor 'pyproject.toml' found."
|
||||
f'{req_name} does not appear to be a Python project: '
|
||||
f"neither 'setup.py' nor 'pyproject.toml' found.",
|
||||
)
|
||||
|
||||
if has_pyproject:
|
||||
with open(pyproject_toml, encoding="utf-8") as f:
|
||||
with open(pyproject_toml, encoding='utf-8') as f:
|
||||
pp_toml = tomli.loads(f.read())
|
||||
build_system = pp_toml.get("build-system")
|
||||
build_system = pp_toml.get('build-system')
|
||||
else:
|
||||
build_system = None
|
||||
|
||||
|
|
@ -74,16 +76,16 @@ def load_pyproject_toml(
|
|||
if has_pyproject and not has_setup:
|
||||
if use_pep517 is not None and not use_pep517:
|
||||
raise InstallationError(
|
||||
"Disabling PEP 517 processing is invalid: "
|
||||
"project does not have a setup.py"
|
||||
'Disabling PEP 517 processing is invalid: '
|
||||
'project does not have a setup.py',
|
||||
)
|
||||
use_pep517 = True
|
||||
elif build_system and "build-backend" in build_system:
|
||||
elif build_system and 'build-backend' in build_system:
|
||||
if use_pep517 is not None and not use_pep517:
|
||||
raise InstallationError(
|
||||
"Disabling PEP 517 processing is invalid: "
|
||||
"project specifies a build backend of {} "
|
||||
"in pyproject.toml".format(build_system["build-backend"])
|
||||
'Disabling PEP 517 processing is invalid: '
|
||||
'project specifies a build backend of {} '
|
||||
'in pyproject.toml'.format(build_system['build-backend']),
|
||||
)
|
||||
use_pep517 = True
|
||||
|
||||
|
|
@ -111,8 +113,8 @@ def load_pyproject_toml(
|
|||
# a version of setuptools that supports that backend.
|
||||
|
||||
build_system = {
|
||||
"requires": ["setuptools>=40.8.0", "wheel"],
|
||||
"build-backend": "setuptools.build_meta:__legacy__",
|
||||
'requires': ['setuptools>=40.8.0', 'wheel'],
|
||||
'build-backend': 'setuptools.build_meta:__legacy__',
|
||||
}
|
||||
|
||||
# If we're using PEP 517, we have build system information (either
|
||||
|
|
@ -125,15 +127,15 @@ def load_pyproject_toml(
|
|||
# to PEP 518.
|
||||
|
||||
# Specifying the build-system table but not the requires key is invalid
|
||||
if "requires" not in build_system:
|
||||
if 'requires' not in build_system:
|
||||
raise MissingPyProjectBuildRequires(package=req_name)
|
||||
|
||||
# Error out if requires is not a list of strings
|
||||
requires = build_system["requires"]
|
||||
requires = build_system['requires']
|
||||
if not _is_list_of_str(requires):
|
||||
raise InvalidPyProjectBuildRequires(
|
||||
package=req_name,
|
||||
reason="It is not a list of strings.",
|
||||
reason='It is not a list of strings.',
|
||||
)
|
||||
|
||||
# Each requirement must be valid as per PEP 508
|
||||
|
|
@ -143,12 +145,12 @@ def load_pyproject_toml(
|
|||
except InvalidRequirement as error:
|
||||
raise InvalidPyProjectBuildRequires(
|
||||
package=req_name,
|
||||
reason=f"It contains an invalid requirement: {requirement!r}",
|
||||
reason=f'It contains an invalid requirement: {requirement!r}',
|
||||
) from error
|
||||
|
||||
backend = build_system.get("build-backend")
|
||||
backend_path = build_system.get("backend-path", [])
|
||||
check: List[str] = []
|
||||
backend = build_system.get('build-backend')
|
||||
backend_path = build_system.get('backend-path', [])
|
||||
check: list[str] = []
|
||||
if backend is None:
|
||||
# If the user didn't specify a backend, we assume they want to use
|
||||
# the setuptools backend. But we can't be sure they have included
|
||||
|
|
@ -162,7 +164,7 @@ def load_pyproject_toml(
|
|||
# execute setup.py, but never considered needing to mention the build
|
||||
# tools themselves. The original PEP 518 code had a similar check (but
|
||||
# implemented in a different way).
|
||||
backend = "setuptools.build_meta:__legacy__"
|
||||
check = ["setuptools>=40.8.0", "wheel"]
|
||||
backend = 'setuptools.build_meta:__legacy__'
|
||||
check = ['setuptools>=40.8.0', 'wheel']
|
||||
|
||||
return BuildSystemDetails(requires, backend, check, backend_path)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import logging
|
||||
from typing import Iterator, List, Optional, Sequence, Tuple
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.utils.logging import indent_log
|
||||
|
||||
|
|
@ -9,10 +15,10 @@ from .req_install import InstallRequirement
|
|||
from .req_set import RequirementSet
|
||||
|
||||
__all__ = [
|
||||
"RequirementSet",
|
||||
"InstallRequirement",
|
||||
"parse_requirements",
|
||||
"install_given_reqs",
|
||||
'RequirementSet',
|
||||
'InstallRequirement',
|
||||
'parse_requirements',
|
||||
'install_given_reqs',
|
||||
]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -23,28 +29,28 @@ class InstallationResult:
|
|||
self.name = name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"InstallationResult(name={self.name!r})"
|
||||
return f'InstallationResult(name={self.name!r})'
|
||||
|
||||
|
||||
def _validate_requirements(
|
||||
requirements: List[InstallRequirement],
|
||||
) -> Iterator[Tuple[str, InstallRequirement]]:
|
||||
requirements: list[InstallRequirement],
|
||||
) -> Iterator[tuple[str, InstallRequirement]]:
|
||||
for req in requirements:
|
||||
assert req.name, f"invalid to-be-installed requirement: {req}"
|
||||
assert req.name, f'invalid to-be-installed requirement: {req}'
|
||||
yield req.name, req
|
||||
|
||||
|
||||
def install_given_reqs(
|
||||
requirements: List[InstallRequirement],
|
||||
install_options: List[str],
|
||||
requirements: list[InstallRequirement],
|
||||
install_options: list[str],
|
||||
global_options: Sequence[str],
|
||||
root: Optional[str],
|
||||
home: Optional[str],
|
||||
prefix: Optional[str],
|
||||
root: str | None,
|
||||
home: str | None,
|
||||
prefix: str | None,
|
||||
warn_script_location: bool,
|
||||
use_user_site: bool,
|
||||
pycompile: bool,
|
||||
) -> List[InstallationResult]:
|
||||
) -> list[InstallationResult]:
|
||||
"""
|
||||
Install everything in the given list.
|
||||
|
||||
|
|
@ -54,8 +60,8 @@ def install_given_reqs(
|
|||
|
||||
if to_install:
|
||||
logger.info(
|
||||
"Installing collected packages: %s",
|
||||
", ".join(to_install.keys()),
|
||||
'Installing collected packages: %s',
|
||||
', '.join(to_install.keys()),
|
||||
)
|
||||
|
||||
installed = []
|
||||
|
|
@ -63,7 +69,7 @@ def install_given_reqs(
|
|||
with indent_log():
|
||||
for req_name, requirement in to_install.items():
|
||||
if requirement.should_reinstall:
|
||||
logger.info("Attempting uninstall: %s", req_name)
|
||||
logger.info('Attempting uninstall: %s', req_name)
|
||||
with indent_log():
|
||||
uninstalled_pathset = requirement.uninstall(auto_confirm=True)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -7,18 +7,21 @@ helps creates for better understandability for the rest of the code.
|
|||
These are meant to be used elsewhere within pip to create instances of
|
||||
InstallRequirement.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from typing import Any, Dict, Optional, Set, Tuple, Union
|
||||
|
||||
from pip._vendor.packaging.markers import Marker
|
||||
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
|
||||
from pip._vendor.packaging.specifiers import Specifier
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.models.index import PyPI, TestPyPI
|
||||
from pip._internal.models.index import PyPI
|
||||
from pip._internal.models.index import TestPyPI
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.models.wheel import Wheel
|
||||
from pip._internal.req.req_file import ParsedRequirement
|
||||
|
|
@ -27,20 +30,25 @@ from pip._internal.utils.filetypes import is_archive_file
|
|||
from pip._internal.utils.misc import is_installable_dir
|
||||
from pip._internal.utils.packaging import get_requirement
|
||||
from pip._internal.utils.urls import path_to_url
|
||||
from pip._internal.vcs import is_url, vcs
|
||||
from pip._internal.vcs import is_url
|
||||
from pip._internal.vcs import vcs
|
||||
from pip._vendor.packaging.markers import Marker
|
||||
from pip._vendor.packaging.requirements import InvalidRequirement
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.specifiers import Specifier
|
||||
|
||||
__all__ = [
|
||||
"install_req_from_editable",
|
||||
"install_req_from_line",
|
||||
"parse_editable",
|
||||
'install_req_from_editable',
|
||||
'install_req_from_line',
|
||||
'parse_editable',
|
||||
]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
operators = Specifier._operators.keys()
|
||||
|
||||
|
||||
def _strip_extras(path: str) -> Tuple[str, Optional[str]]:
|
||||
m = re.match(r"^(.+)(\[[^\]]+\])$", path)
|
||||
def _strip_extras(path: str) -> tuple[str, str | None]:
|
||||
m = re.match(r'^(.+)(\[[^\]]+\])$', path)
|
||||
extras = None
|
||||
if m:
|
||||
path_no_extras = m.group(1)
|
||||
|
|
@ -51,13 +59,13 @@ def _strip_extras(path: str) -> Tuple[str, Optional[str]]:
|
|||
return path_no_extras, extras
|
||||
|
||||
|
||||
def convert_extras(extras: Optional[str]) -> Set[str]:
|
||||
def convert_extras(extras: str | None) -> set[str]:
|
||||
if not extras:
|
||||
return set()
|
||||
return get_requirement("placeholder" + extras.lower()).extras
|
||||
return get_requirement('placeholder' + extras.lower()).extras
|
||||
|
||||
|
||||
def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
|
||||
def parse_editable(editable_req: str) -> tuple[str | None, str, set[str]]:
|
||||
"""Parses an editable requirement into:
|
||||
- a requirement name
|
||||
- an URL
|
||||
|
|
@ -77,37 +85,37 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
|
|||
# Treating it as code that has already been checked out
|
||||
url_no_extras = path_to_url(url_no_extras)
|
||||
|
||||
if url_no_extras.lower().startswith("file:"):
|
||||
if url_no_extras.lower().startswith('file:'):
|
||||
package_name = Link(url_no_extras).egg_fragment
|
||||
if extras:
|
||||
return (
|
||||
package_name,
|
||||
url_no_extras,
|
||||
get_requirement("placeholder" + extras.lower()).extras,
|
||||
get_requirement('placeholder' + extras.lower()).extras,
|
||||
)
|
||||
else:
|
||||
return package_name, url_no_extras, set()
|
||||
|
||||
for version_control in vcs:
|
||||
if url.lower().startswith(f"{version_control}:"):
|
||||
url = f"{version_control}+{url}"
|
||||
if url.lower().startswith(f'{version_control}:'):
|
||||
url = f'{version_control}+{url}'
|
||||
break
|
||||
|
||||
link = Link(url)
|
||||
|
||||
if not link.is_vcs:
|
||||
backends = ", ".join(vcs.all_schemes)
|
||||
backends = ', '.join(vcs.all_schemes)
|
||||
raise InstallationError(
|
||||
f"{editable_req} is not a valid editable requirement. "
|
||||
f"It should either be a path to a local project or a VCS URL "
|
||||
f"(beginning with {backends})."
|
||||
f'{editable_req} is not a valid editable requirement. '
|
||||
f'It should either be a path to a local project or a VCS URL '
|
||||
f'(beginning with {backends}).',
|
||||
)
|
||||
|
||||
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)
|
||||
'with #egg=your_package_name'.format(editable_req),
|
||||
)
|
||||
return package_name, url, set()
|
||||
|
||||
|
|
@ -121,21 +129,21 @@ def check_first_requirement_in_file(filename: str) -> None:
|
|||
:raises InvalidRequirement: If the first meaningful line cannot be parsed
|
||||
as an requirement.
|
||||
"""
|
||||
with open(filename, encoding="utf-8", errors="ignore") as f:
|
||||
with open(filename, encoding='utf-8', errors='ignore') as f:
|
||||
# Create a steppable iterator, so we can handle \-continuations.
|
||||
lines = (
|
||||
line
|
||||
for line in (line.strip() for line in f)
|
||||
if line and not line.startswith("#") # Skip blank lines/comments.
|
||||
if line and not line.startswith('#') # Skip blank lines/comments.
|
||||
)
|
||||
|
||||
for line in lines:
|
||||
# Drop comments -- a hash without a space may be in a URL.
|
||||
if " #" in line:
|
||||
line = line[: line.find(" #")]
|
||||
if ' #' in line:
|
||||
line = line[: line.find(' #')]
|
||||
# If there is a line continuation, drop it, and append the next line.
|
||||
if line.endswith("\\"):
|
||||
line = line[:-2].strip() + next(lines, "")
|
||||
if line.endswith('\\'):
|
||||
line = line[:-2].strip() + next(lines, '')
|
||||
Requirement(line)
|
||||
return
|
||||
|
||||
|
|
@ -148,7 +156,7 @@ def deduce_helpful_msg(req: str) -> str:
|
|||
"""
|
||||
if not os.path.exists(req):
|
||||
return f" File '{req}' does not exist."
|
||||
msg = " The path does exist. "
|
||||
msg = ' The path does exist. '
|
||||
# Try to parse and check if it is a requirements file.
|
||||
try:
|
||||
check_first_requirement_in_file(req)
|
||||
|
|
@ -156,11 +164,11 @@ def deduce_helpful_msg(req: str) -> str:
|
|||
logger.debug("Cannot parse '%s' as requirements file", req)
|
||||
else:
|
||||
msg += (
|
||||
f"The argument you provided "
|
||||
f"({req}) appears to be a"
|
||||
f" requirements file. If that is the"
|
||||
f'The argument you provided '
|
||||
f'({req}) appears to be a'
|
||||
f' requirements file. If that is the'
|
||||
f" case, use the '-r' flag to install"
|
||||
f" the packages specified within it."
|
||||
f' the packages specified within it.'
|
||||
)
|
||||
return msg
|
||||
|
||||
|
|
@ -168,10 +176,10 @@ def deduce_helpful_msg(req: str) -> str:
|
|||
class RequirementParts:
|
||||
def __init__(
|
||||
self,
|
||||
requirement: Optional[Requirement],
|
||||
link: Optional[Link],
|
||||
markers: Optional[Marker],
|
||||
extras: Set[str],
|
||||
requirement: Requirement | None,
|
||||
link: Link | None,
|
||||
markers: Marker | None,
|
||||
extras: set[str],
|
||||
):
|
||||
self.requirement = requirement
|
||||
self.link = link
|
||||
|
|
@ -184,7 +192,7 @@ def parse_req_from_editable(editable_req: str) -> RequirementParts:
|
|||
|
||||
if name is not None:
|
||||
try:
|
||||
req: Optional[Requirement] = Requirement(name)
|
||||
req: Requirement | None = Requirement(name)
|
||||
except InvalidRequirement:
|
||||
raise InstallationError(f"Invalid requirement: '{name}'")
|
||||
else:
|
||||
|
|
@ -200,10 +208,10 @@ def parse_req_from_editable(editable_req: str) -> RequirementParts:
|
|||
|
||||
def install_req_from_editable(
|
||||
editable_req: str,
|
||||
comes_from: Optional[Union[InstallRequirement, str]] = None,
|
||||
use_pep517: Optional[bool] = None,
|
||||
comes_from: InstallRequirement | str | None = None,
|
||||
use_pep517: bool | None = None,
|
||||
isolated: bool = False,
|
||||
options: Optional[Dict[str, Any]] = None,
|
||||
options: dict[str, Any] | None = None,
|
||||
constraint: bool = False,
|
||||
user_supplied: bool = False,
|
||||
permit_editable_wheels: bool = False,
|
||||
|
|
@ -221,9 +229,9 @@ def install_req_from_editable(
|
|||
constraint=constraint,
|
||||
use_pep517=use_pep517,
|
||||
isolated=isolated,
|
||||
install_options=options.get("install_options", []) if options else [],
|
||||
global_options=options.get("global_options", []) if options else [],
|
||||
hash_options=options.get("hashes", {}) if options else {},
|
||||
install_options=options.get('install_options', []) if options else [],
|
||||
global_options=options.get('global_options', []) if options else [],
|
||||
hash_options=options.get('hashes', {}) if options else {},
|
||||
extras=parts.extras,
|
||||
)
|
||||
|
||||
|
|
@ -242,12 +250,12 @@ def _looks_like_path(name: str) -> bool:
|
|||
return True
|
||||
if os.path.altsep is not None and os.path.altsep in name:
|
||||
return True
|
||||
if name.startswith("."):
|
||||
if name.startswith('.'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _get_url_from_path(path: str, name: str) -> Optional[str]:
|
||||
def _get_url_from_path(path: str, name: str) -> str | None:
|
||||
"""
|
||||
First, it checks whether a provided path is an installable directory. If it
|
||||
is, returns the path.
|
||||
|
|
@ -263,29 +271,29 @@ def _get_url_from_path(path: str, name: str) -> Optional[str]:
|
|||
# now that it is done in load_pyproject_toml too.
|
||||
raise InstallationError(
|
||||
f"Directory {name!r} is not installable. Neither 'setup.py' "
|
||||
"nor 'pyproject.toml' found."
|
||||
"nor 'pyproject.toml' found.",
|
||||
)
|
||||
if not is_archive_file(path):
|
||||
return None
|
||||
if os.path.isfile(path):
|
||||
return path_to_url(path)
|
||||
urlreq_parts = name.split("@", 1)
|
||||
urlreq_parts = name.split('@', 1)
|
||||
if len(urlreq_parts) >= 2 and not _looks_like_path(urlreq_parts[0]):
|
||||
# If the path contains '@' and the part before it does not look
|
||||
# like a path, try to treat it as a PEP 440 URL req instead.
|
||||
return None
|
||||
logger.warning(
|
||||
"Requirement %r looks like a filename, but the file does not exist",
|
||||
'Requirement %r looks like a filename, but the file does not exist',
|
||||
name,
|
||||
)
|
||||
return path_to_url(path)
|
||||
|
||||
|
||||
def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementParts:
|
||||
def parse_req_from_line(name: str, line_source: str | None) -> RequirementParts:
|
||||
if is_url(name):
|
||||
marker_sep = "; "
|
||||
marker_sep = '; '
|
||||
else:
|
||||
marker_sep = ";"
|
||||
marker_sep = ';'
|
||||
if marker_sep in name:
|
||||
name, markers_as_string = name.split(marker_sep, 1)
|
||||
markers_as_string = markers_as_string.strip()
|
||||
|
|
@ -312,12 +320,12 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
|
|||
# it's a local file, dir, or url
|
||||
if link:
|
||||
# Handle relative file URLs
|
||||
if link.scheme == "file" and re.search(r"\.\./", link.url):
|
||||
if link.scheme == 'file' and re.search(r'\.\./', link.url):
|
||||
link = Link(path_to_url(os.path.normpath(os.path.abspath(link.path))))
|
||||
# wheel file
|
||||
if link.is_wheel:
|
||||
wheel = Wheel(link.filename) # can raise InvalidWheelFilename
|
||||
req_as_string = f"{wheel.name}=={wheel.version}"
|
||||
req_as_string = f'{wheel.name}=={wheel.version}'
|
||||
else:
|
||||
# set the req to the egg fragment. when it's not there, this
|
||||
# will become an 'unnamed' requirement
|
||||
|
|
@ -332,24 +340,24 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
|
|||
def with_source(text: str) -> str:
|
||||
if not line_source:
|
||||
return text
|
||||
return f"{text} (from {line_source})"
|
||||
return f'{text} (from {line_source})'
|
||||
|
||||
def _parse_req_string(req_as_string: str) -> Requirement:
|
||||
try:
|
||||
req = get_requirement(req_as_string)
|
||||
except InvalidRequirement:
|
||||
if os.path.sep in req_as_string:
|
||||
add_msg = "It looks like a path."
|
||||
add_msg = 'It looks like a path.'
|
||||
add_msg += deduce_helpful_msg(req_as_string)
|
||||
elif "=" in req_as_string and not any(
|
||||
elif '=' in req_as_string and not any(
|
||||
op in req_as_string for op in operators
|
||||
):
|
||||
add_msg = "= is not a valid operator. Did you mean == ?"
|
||||
add_msg = '= is not a valid operator. Did you mean == ?'
|
||||
else:
|
||||
add_msg = ""
|
||||
msg = with_source(f"Invalid requirement: {req_as_string!r}")
|
||||
add_msg = ''
|
||||
msg = with_source(f'Invalid requirement: {req_as_string!r}')
|
||||
if add_msg:
|
||||
msg += f"\nHint: {add_msg}"
|
||||
msg += f'\nHint: {add_msg}'
|
||||
raise InstallationError(msg)
|
||||
else:
|
||||
# Deprecate extras after specifiers: "name>=1.0[extras]"
|
||||
|
|
@ -358,13 +366,13 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
|
|||
# RequirementParts
|
||||
for spec in req.specifier:
|
||||
spec_str = str(spec)
|
||||
if spec_str.endswith("]"):
|
||||
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)
|
||||
req: Requirement | None = _parse_req_string(req_as_string)
|
||||
else:
|
||||
req = None
|
||||
|
||||
|
|
@ -373,12 +381,12 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
|
|||
|
||||
def install_req_from_line(
|
||||
name: str,
|
||||
comes_from: Optional[Union[str, InstallRequirement]] = None,
|
||||
use_pep517: Optional[bool] = None,
|
||||
comes_from: str | InstallRequirement | None = None,
|
||||
use_pep517: bool | None = None,
|
||||
isolated: bool = False,
|
||||
options: Optional[Dict[str, Any]] = None,
|
||||
options: dict[str, Any] | None = None,
|
||||
constraint: bool = False,
|
||||
line_source: Optional[str] = None,
|
||||
line_source: str | None = None,
|
||||
user_supplied: bool = False,
|
||||
) -> InstallRequirement:
|
||||
"""Creates an InstallRequirement from a name, which might be a
|
||||
|
|
@ -396,9 +404,9 @@ def install_req_from_line(
|
|||
markers=parts.markers,
|
||||
use_pep517=use_pep517,
|
||||
isolated=isolated,
|
||||
install_options=options.get("install_options", []) if options else [],
|
||||
global_options=options.get("global_options", []) if options else [],
|
||||
hash_options=options.get("hashes", {}) if options else {},
|
||||
install_options=options.get('install_options', []) if options else [],
|
||||
global_options=options.get('global_options', []) if options else [],
|
||||
hash_options=options.get('hashes', {}) if options else {},
|
||||
constraint=constraint,
|
||||
extras=parts.extras,
|
||||
user_supplied=user_supplied,
|
||||
|
|
@ -407,9 +415,9 @@ def install_req_from_line(
|
|||
|
||||
def install_req_from_req_string(
|
||||
req_string: str,
|
||||
comes_from: Optional[InstallRequirement] = None,
|
||||
comes_from: InstallRequirement | None = None,
|
||||
isolated: bool = False,
|
||||
use_pep517: Optional[bool] = None,
|
||||
use_pep517: bool | None = None,
|
||||
user_supplied: bool = False,
|
||||
) -> InstallRequirement:
|
||||
try:
|
||||
|
|
@ -422,16 +430,16 @@ def install_req_from_req_string(
|
|||
TestPyPI.file_storage_domain,
|
||||
]
|
||||
if (
|
||||
req.url
|
||||
and comes_from
|
||||
and comes_from.link
|
||||
and comes_from.link.netloc in domains_not_allowed
|
||||
req.url and
|
||||
comes_from and
|
||||
comes_from.link and
|
||||
comes_from.link.netloc in domains_not_allowed
|
||||
):
|
||||
# Explicitly disallow pypi packages that depend on external urls
|
||||
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)
|
||||
'Packages installed from PyPI cannot depend on packages '
|
||||
'which are not also hosted on PyPI.\n'
|
||||
'{} depends on {} '.format(comes_from.name, req),
|
||||
)
|
||||
|
||||
return InstallRequirement(
|
||||
|
|
@ -446,7 +454,7 @@ def install_req_from_req_string(
|
|||
def install_req_from_parsed_requirement(
|
||||
parsed_req: ParsedRequirement,
|
||||
isolated: bool = False,
|
||||
use_pep517: Optional[bool] = None,
|
||||
use_pep517: bool | None = None,
|
||||
user_supplied: bool = False,
|
||||
) -> InstallRequirement:
|
||||
if parsed_req.is_editable:
|
||||
|
|
@ -474,7 +482,7 @@ def install_req_from_parsed_requirement(
|
|||
|
||||
|
||||
def install_req_from_link_and_ireq(
|
||||
link: Link, ireq: InstallRequirement
|
||||
link: Link, ireq: InstallRequirement,
|
||||
) -> InstallRequirement:
|
||||
return InstallRequirement(
|
||||
req=ireq.req,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
Requirements file parsing
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import optparse
|
||||
import os
|
||||
|
|
@ -8,20 +9,19 @@ import re
|
|||
import shlex
|
||||
import urllib.parse
|
||||
from optparse import Values
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
)
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.exceptions import InstallationError, RequirementsFileParseError
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import 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
|
||||
|
|
@ -35,22 +35,22 @@ if TYPE_CHECKING:
|
|||
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
|
||||
__all__ = ["parse_requirements"]
|
||||
__all__ = ['parse_requirements']
|
||||
|
||||
ReqFileLines = Iterable[Tuple[int, str]]
|
||||
|
||||
LineParser = Callable[[str], Tuple[str, Values]]
|
||||
|
||||
SCHEME_RE = re.compile(r"^(http|https|file):", re.I)
|
||||
COMMENT_RE = re.compile(r"(^|\s+)#.*$")
|
||||
SCHEME_RE = re.compile(r'^(http|https|file):', re.I)
|
||||
COMMENT_RE = re.compile(r'(^|\s+)#.*$')
|
||||
|
||||
# Matches environment variable-style values in '${MY_VARIABLE_1}' with the
|
||||
# variable name consisting of only uppercase letters, digits or the '_'
|
||||
# (underscore). This follows the POSIX standard defined in IEEE Std 1003.1,
|
||||
# 2013 Edition.
|
||||
ENV_VAR_RE = re.compile(r"(?P<var>\$\{(?P<name>[A-Z0-9_]+)\})")
|
||||
ENV_VAR_RE = re.compile(r'(?P<var>\$\{(?P<name>[A-Z0-9_]+)\})')
|
||||
|
||||
SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [
|
||||
SUPPORTED_OPTIONS: list[Callable[..., optparse.Option]] = [
|
||||
cmdoptions.index_url,
|
||||
cmdoptions.extra_index_url,
|
||||
cmdoptions.no_index,
|
||||
|
|
@ -68,7 +68,7 @@ SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [
|
|||
]
|
||||
|
||||
# options to be passed to requirements
|
||||
SUPPORTED_OPTIONS_REQ: List[Callable[..., optparse.Option]] = [
|
||||
SUPPORTED_OPTIONS_REQ: list[Callable[..., optparse.Option]] = [
|
||||
cmdoptions.install_options,
|
||||
cmdoptions.global_options,
|
||||
cmdoptions.hash,
|
||||
|
|
@ -85,8 +85,8 @@ class ParsedRequirement:
|
|||
is_editable: bool,
|
||||
comes_from: str,
|
||||
constraint: bool,
|
||||
options: Optional[Dict[str, Any]] = None,
|
||||
line_source: Optional[str] = None,
|
||||
options: dict[str, Any] | None = None,
|
||||
line_source: str | None = None,
|
||||
) -> None:
|
||||
self.requirement = requirement
|
||||
self.is_editable = is_editable
|
||||
|
|
@ -126,8 +126,8 @@ class ParsedLine:
|
|||
def parse_requirements(
|
||||
filename: str,
|
||||
session: PipSession,
|
||||
finder: Optional["PackageFinder"] = None,
|
||||
options: Optional[optparse.Values] = None,
|
||||
finder: PackageFinder | None = None,
|
||||
options: optparse.Values | None = None,
|
||||
constraint: bool = False,
|
||||
) -> Iterator[ParsedRequirement]:
|
||||
"""Parse a requirements file and yield ParsedRequirement instances.
|
||||
|
|
@ -144,7 +144,7 @@ def parse_requirements(
|
|||
|
||||
for parsed_line in parser.parse(filename, constraint):
|
||||
parsed_req = handle_line(
|
||||
parsed_line, options=options, finder=finder, session=session
|
||||
parsed_line, options=options, finder=finder, session=session,
|
||||
)
|
||||
if parsed_req is not None:
|
||||
yield parsed_req
|
||||
|
|
@ -164,12 +164,12 @@ def preprocess(content: str) -> ReqFileLines:
|
|||
|
||||
def handle_requirement_line(
|
||||
line: ParsedLine,
|
||||
options: Optional[optparse.Values] = None,
|
||||
options: optparse.Values | None = None,
|
||||
) -> ParsedRequirement:
|
||||
|
||||
# preserve for the nested code path
|
||||
line_comes_from = "{} {} (line {})".format(
|
||||
"-c" if line.constraint else "-r",
|
||||
line_comes_from = '{} {} (line {})'.format(
|
||||
'-c' if line.constraint else '-r',
|
||||
line.filename,
|
||||
line.lineno,
|
||||
)
|
||||
|
|
@ -196,7 +196,7 @@ def handle_requirement_line(
|
|||
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}"
|
||||
line_source = f'line {line.lineno} of {line.filename}'
|
||||
return ParsedRequirement(
|
||||
requirement=line.requirement,
|
||||
is_editable=line.is_editable,
|
||||
|
|
@ -211,9 +211,9 @@ def handle_option_line(
|
|||
opts: Values,
|
||||
filename: str,
|
||||
lineno: int,
|
||||
finder: Optional["PackageFinder"] = None,
|
||||
options: Optional[optparse.Values] = None,
|
||||
session: Optional[PipSession] = None,
|
||||
finder: PackageFinder | None = None,
|
||||
options: optparse.Values | None = None,
|
||||
session: PipSession | None = None,
|
||||
) -> None:
|
||||
|
||||
if options:
|
||||
|
|
@ -264,16 +264,16 @@ def handle_option_line(
|
|||
|
||||
if session:
|
||||
for host in opts.trusted_hosts or []:
|
||||
source = f"line {lineno} of {filename}"
|
||||
source = f'line {lineno} of {filename}'
|
||||
session.add_trusted_host(host, source=source)
|
||||
|
||||
|
||||
def handle_line(
|
||||
line: ParsedLine,
|
||||
options: Optional[optparse.Values] = None,
|
||||
finder: Optional["PackageFinder"] = None,
|
||||
session: Optional[PipSession] = None,
|
||||
) -> Optional[ParsedRequirement]:
|
||||
options: optparse.Values | None = None,
|
||||
finder: PackageFinder | None = None,
|
||||
session: PipSession | None = None,
|
||||
) -> ParsedRequirement | None:
|
||||
"""Handle a single parsed requirements line; This can result in
|
||||
creating/yielding requirements, or updating the finder.
|
||||
|
||||
|
|
@ -326,7 +326,7 @@ class RequirementsFileParser:
|
|||
yield from self._parse_and_recurse(filename, constraint)
|
||||
|
||||
def _parse_and_recurse(
|
||||
self, filename: str, constraint: bool
|
||||
self, filename: str, constraint: bool,
|
||||
) -> Iterator[ParsedLine]:
|
||||
for line in self._parse_file(filename, constraint):
|
||||
if not line.is_requirement and (
|
||||
|
|
@ -366,7 +366,7 @@ class RequirementsFileParser:
|
|||
args_str, opts = self._line_parser(line)
|
||||
except OptionParsingError as e:
|
||||
# add offending line
|
||||
msg = f"Invalid requirement: {line}\n{e.msg}"
|
||||
msg = f'Invalid requirement: {line}\n{e.msg}'
|
||||
raise RequirementsFileParseError(msg)
|
||||
|
||||
yield ParsedLine(
|
||||
|
|
@ -378,8 +378,8 @@ class RequirementsFileParser:
|
|||
)
|
||||
|
||||
|
||||
def get_line_parser(finder: Optional["PackageFinder"]) -> LineParser:
|
||||
def parse_line(line: str) -> Tuple[str, Values]:
|
||||
def get_line_parser(finder: PackageFinder | None) -> LineParser:
|
||||
def parse_line(line: str) -> tuple[str, Values]:
|
||||
# Build new parser for each line since it accumulates appendable
|
||||
# options.
|
||||
parser = build_parser()
|
||||
|
|
@ -397,21 +397,21 @@ def get_line_parser(finder: Optional["PackageFinder"]) -> LineParser:
|
|||
return parse_line
|
||||
|
||||
|
||||
def break_args_options(line: str) -> Tuple[str, str]:
|
||||
def break_args_options(line: str) -> tuple[str, str]:
|
||||
"""Break up the line into an args and options string. We only want to shlex
|
||||
(and then optparse) the options, not the args. args can contain markers
|
||||
which are corrupted by shlex.
|
||||
"""
|
||||
tokens = line.split(" ")
|
||||
tokens = line.split(' ')
|
||||
args = []
|
||||
options = tokens[:]
|
||||
for token in tokens:
|
||||
if token.startswith("-") or token.startswith("--"):
|
||||
if token.startswith('-') or token.startswith('--'):
|
||||
break
|
||||
else:
|
||||
args.append(token)
|
||||
options.pop(0)
|
||||
return " ".join(args), " ".join(options)
|
||||
return ' '.join(args), ' '.join(options)
|
||||
|
||||
|
||||
class OptionParsingError(Exception):
|
||||
|
|
@ -432,7 +432,7 @@ def build_parser() -> optparse.OptionParser:
|
|||
|
||||
# By default optparse sys.exits on parsing errors. We want to wrap
|
||||
# that in our own exception.
|
||||
def parser_exit(self: Any, msg: str) -> "NoReturn":
|
||||
def parser_exit(self: Any, msg: str) -> NoReturn:
|
||||
raise OptionParsingError(msg)
|
||||
|
||||
# NOTE: mypy disallows assigning to a method
|
||||
|
|
@ -447,28 +447,28 @@ def join_lines(lines_enum: ReqFileLines) -> ReqFileLines:
|
|||
comments). The joined line takes on the index of the first line.
|
||||
"""
|
||||
primary_line_number = None
|
||||
new_line: List[str] = []
|
||||
new_line: list[str] = []
|
||||
for line_number, line in lines_enum:
|
||||
if not line.endswith("\\") or COMMENT_RE.match(line):
|
||||
if not line.endswith('\\') or COMMENT_RE.match(line):
|
||||
if COMMENT_RE.match(line):
|
||||
# this ensures comments are always matched later
|
||||
line = " " + line
|
||||
line = ' ' + line
|
||||
if new_line:
|
||||
new_line.append(line)
|
||||
assert primary_line_number is not None
|
||||
yield primary_line_number, "".join(new_line)
|
||||
yield primary_line_number, ''.join(new_line)
|
||||
new_line = []
|
||||
else:
|
||||
yield line_number, line
|
||||
else:
|
||||
if not new_line:
|
||||
primary_line_number = line_number
|
||||
new_line.append(line.strip("\\"))
|
||||
new_line.append(line.strip('\\'))
|
||||
|
||||
# last line contains \
|
||||
if new_line:
|
||||
assert primary_line_number is not None
|
||||
yield primary_line_number, "".join(new_line)
|
||||
yield primary_line_number, ''.join(new_line)
|
||||
|
||||
# TODO: handle space after '\'.
|
||||
|
||||
|
|
@ -478,7 +478,7 @@ def ignore_comments(lines_enum: ReqFileLines) -> ReqFileLines:
|
|||
Strips comments and filter empty lines.
|
||||
"""
|
||||
for line_number, line in lines_enum:
|
||||
line = COMMENT_RE.sub("", line)
|
||||
line = COMMENT_RE.sub('', line)
|
||||
line = line.strip()
|
||||
if line:
|
||||
yield line_number, line
|
||||
|
|
@ -511,7 +511,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.
|
||||
|
|
@ -522,15 +522,15 @@ def get_file_content(url: str, session: PipSession) -> Tuple[str, str]:
|
|||
scheme = get_url_scheme(url)
|
||||
|
||||
# Pip has special support for file:// URLs (LocalFSAdapter).
|
||||
if scheme in ["http", "https", "file"]:
|
||||
if scheme in ['http', 'https', 'file']:
|
||||
resp = session.get(url)
|
||||
raise_for_status(resp)
|
||||
return resp.url, resp.text
|
||||
|
||||
# Assume this is a bare path.
|
||||
try:
|
||||
with open(url, "rb") as f:
|
||||
with open(url, 'rb') as f:
|
||||
content = auto_decode(f.read())
|
||||
except OSError as exc:
|
||||
raise InstallationError(f"Could not open requirements file: {exc}")
|
||||
raise InstallationError(f'Could not open requirements file: {exc}')
|
||||
return url, content
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import logging
|
||||
|
|
@ -8,24 +9,23 @@ import shutil
|
|||
import sys
|
||||
import uuid
|
||||
import zipfile
|
||||
from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union
|
||||
from typing import Any
|
||||
from typing import Collection
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Union
|
||||
|
||||
from pip._vendor.packaging.markers import Marker
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import Version
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment
|
||||
from pip._internal.exceptions import InstallationError, LegacyInstallFailure
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.build_env import NoOpBuildEnvironment
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import LegacyInstallFailure
|
||||
from pip._internal.locations import get_scheme
|
||||
from pip._internal.metadata import (
|
||||
BaseDistribution,
|
||||
get_default_environment,
|
||||
get_directory_distribution,
|
||||
)
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.metadata import get_default_environment
|
||||
from pip._internal.metadata import get_directory_distribution
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.operations.build.metadata import generate_metadata
|
||||
from pip._internal.operations.build.metadata_editable import generate_editable_metadata
|
||||
|
|
@ -37,26 +37,31 @@ from pip._internal.operations.install.editable_legacy import (
|
|||
)
|
||||
from pip._internal.operations.install.legacy import install as install_legacy
|
||||
from pip._internal.operations.install.wheel import install_wheel
|
||||
from pip._internal.pyproject import load_pyproject_toml, make_pyproject_path
|
||||
from pip._internal.pyproject import load_pyproject_toml
|
||||
from pip._internal.pyproject import make_pyproject_path
|
||||
from pip._internal.req.req_uninstall import UninstallPathSet
|
||||
from pip._internal.utils.deprecation import deprecated
|
||||
from pip._internal.utils.direct_url_helpers import (
|
||||
direct_url_for_editable,
|
||||
direct_url_from_link,
|
||||
)
|
||||
from pip._internal.utils.direct_url_helpers import direct_url_for_editable
|
||||
from pip._internal.utils.direct_url_helpers import direct_url_from_link
|
||||
from pip._internal.utils.hashes import Hashes
|
||||
from pip._internal.utils.misc import (
|
||||
ask_path_exists,
|
||||
backup_dir,
|
||||
display_path,
|
||||
hide_url,
|
||||
redact_auth_from_url,
|
||||
)
|
||||
from pip._internal.utils.misc import ask_path_exists
|
||||
from pip._internal.utils.misc import backup_dir
|
||||
from pip._internal.utils.misc import display_path
|
||||
from pip._internal.utils.misc import hide_url
|
||||
from pip._internal.utils.misc import redact_auth_from_url
|
||||
from pip._internal.utils.packaging import safe_extra
|
||||
from pip._internal.utils.subprocess import runner_with_spinner_message
|
||||
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
|
||||
from pip._internal.utils.temp_dir import tempdir_kinds
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
from pip._internal.vcs import vcs
|
||||
from pip._vendor.packaging.markers import Marker
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.version import parse as parse_version
|
||||
from pip._vendor.packaging.version import Version
|
||||
from pip._vendor.pep517.wrappers import Pep517HookCaller
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -70,16 +75,16 @@ class InstallRequirement:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
req: Optional[Requirement],
|
||||
comes_from: Optional[Union[str, "InstallRequirement"]],
|
||||
req: Requirement | None,
|
||||
comes_from: str | InstallRequirement | None,
|
||||
editable: bool = False,
|
||||
link: Optional[Link] = None,
|
||||
markers: Optional[Marker] = None,
|
||||
use_pep517: Optional[bool] = None,
|
||||
link: Link | None = None,
|
||||
markers: Marker | None = None,
|
||||
use_pep517: bool | None = None,
|
||||
isolated: bool = False,
|
||||
install_options: Optional[List[str]] = None,
|
||||
global_options: Optional[List[str]] = None,
|
||||
hash_options: Optional[Dict[str, List[str]]] = None,
|
||||
install_options: list[str] | None = None,
|
||||
global_options: list[str] | None = None,
|
||||
hash_options: dict[str, list[str]] | None = None,
|
||||
constraint: bool = False,
|
||||
extras: Collection[str] = (),
|
||||
user_supplied: bool = False,
|
||||
|
|
@ -91,14 +96,14 @@ class InstallRequirement:
|
|||
self.constraint = constraint
|
||||
self.editable = editable
|
||||
self.permit_editable_wheels = permit_editable_wheels
|
||||
self.legacy_install_reason: Optional[int] = None
|
||||
self.legacy_install_reason: int | None = None
|
||||
|
||||
# source_dir is the local directory where the linked requirement is
|
||||
# located, or unpacked. In case unpacking is needed, creating and
|
||||
# populating source_dir is done by the RequirementPreparer. Note this
|
||||
# is not necessarily the directory where pyproject.toml or setup.py is
|
||||
# located - that one is obtained via unpacked_source_directory.
|
||||
self.source_dir: Optional[str] = None
|
||||
self.source_dir: str | None = None
|
||||
if self.editable:
|
||||
assert link
|
||||
if link.is_file:
|
||||
|
|
@ -111,7 +116,7 @@ class InstallRequirement:
|
|||
self.original_link_is_in_wheel_cache = False
|
||||
|
||||
# Path to any downloaded or already-existing package.
|
||||
self.local_file_path: Optional[str] = None
|
||||
self.local_file_path: str | None = None
|
||||
if self.link and self.link.is_file:
|
||||
self.local_file_path = self.link.file_path
|
||||
|
||||
|
|
@ -126,14 +131,14 @@ class InstallRequirement:
|
|||
self.markers = markers
|
||||
|
||||
# This holds the Distribution object if this requirement is already installed.
|
||||
self.satisfied_by: Optional[BaseDistribution] = None
|
||||
self.satisfied_by: BaseDistribution | None = None
|
||||
# Whether the installation process should try to uninstall an existing
|
||||
# distribution before installing this requirement.
|
||||
self.should_reinstall = False
|
||||
# Temporary build location
|
||||
self._temp_build_dir: Optional[TempDirectory] = None
|
||||
self._temp_build_dir: TempDirectory | None = None
|
||||
# Set to True after successful installation
|
||||
self.install_succeeded: Optional[bool] = None
|
||||
self.install_succeeded: bool | None = None
|
||||
# Supplied options
|
||||
self.install_options = install_options if install_options else []
|
||||
self.global_options = global_options if global_options else []
|
||||
|
|
@ -152,16 +157,16 @@ class InstallRequirement:
|
|||
# gets stored. We need this to pass to build_wheel, so the backend
|
||||
# can ensure that the wheel matches the metadata (see the PEP for
|
||||
# details).
|
||||
self.metadata_directory: Optional[str] = None
|
||||
self.metadata_directory: str | None = None
|
||||
|
||||
# The static build requirements (from pyproject.toml)
|
||||
self.pyproject_requires: Optional[List[str]] = None
|
||||
self.pyproject_requires: list[str] | None = None
|
||||
|
||||
# Build requirements that we will check are available
|
||||
self.requirements_to_check: List[str] = []
|
||||
self.requirements_to_check: list[str] = []
|
||||
|
||||
# The PEP 517 backend we should use to build the project
|
||||
self.pep517_backend: Optional[Pep517HookCaller] = None
|
||||
self.pep517_backend: Pep517HookCaller | None = None
|
||||
|
||||
# Are we using PEP 517 for this requirement?
|
||||
# After pyproject.toml has been loaded, the only valid values are True
|
||||
|
|
@ -177,25 +182,25 @@ class InstallRequirement:
|
|||
if self.req:
|
||||
s = str(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:
|
||||
s = "<InstallRequirement>"
|
||||
s = '<InstallRequirement>'
|
||||
if self.satisfied_by is not None:
|
||||
s += " in {}".format(display_path(self.satisfied_by.location))
|
||||
s += f' in {display_path(self.satisfied_by.location)}'
|
||||
if self.comes_from:
|
||||
if isinstance(self.comes_from, str):
|
||||
comes_from: Optional[str] = self.comes_from
|
||||
comes_from: str | None = self.comes_from
|
||||
else:
|
||||
comes_from = self.comes_from.from_path()
|
||||
if comes_from:
|
||||
s += f" (from {comes_from})"
|
||||
s += f' (from {comes_from})'
|
||||
return s
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<{} object: {} editable={!r}>".format(
|
||||
self.__class__.__name__, str(self), self.editable
|
||||
return '<{} object: {} editable={!r}>'.format(
|
||||
self.__class__.__name__, str(self), self.editable,
|
||||
)
|
||||
|
||||
def format_debug(self) -> str:
|
||||
|
|
@ -203,30 +208,30 @@ class InstallRequirement:
|
|||
attributes = vars(self)
|
||||
names = sorted(attributes)
|
||||
|
||||
state = ("{}={!r}".format(attr, attributes[attr]) for attr in sorted(names))
|
||||
return "<{name} object: {{{state}}}>".format(
|
||||
state = (f'{attr}={attributes[attr]!r}' for attr in sorted(names))
|
||||
return '<{name} object: {{{state}}}>'.format(
|
||||
name=self.__class__.__name__,
|
||||
state=", ".join(state),
|
||||
state=', '.join(state),
|
||||
)
|
||||
|
||||
# Things that are valid for all kinds of requirements?
|
||||
@property
|
||||
def name(self) -> Optional[str]:
|
||||
def name(self) -> str | None:
|
||||
if self.req is None:
|
||||
return None
|
||||
return self.req.name
|
||||
|
||||
@functools.lru_cache() # use cached_property in python 3.8+
|
||||
@functools.lru_cache # use cached_property in python 3.8+
|
||||
def supports_pyproject_editable(self) -> bool:
|
||||
if not self.use_pep517:
|
||||
return False
|
||||
assert self.pep517_backend
|
||||
with self.build_env:
|
||||
runner = runner_with_spinner_message(
|
||||
"Checking if build backend supports build_editable"
|
||||
'Checking if build backend supports build_editable',
|
||||
)
|
||||
with self.pep517_backend.subprocess_runner(runner):
|
||||
return "build_editable" in self.pep517_backend._supported_features()
|
||||
return 'build_editable' in self.pep517_backend._supported_features()
|
||||
|
||||
@property
|
||||
def specifier(self) -> SpecifierSet:
|
||||
|
|
@ -239,16 +244,16 @@ class InstallRequirement:
|
|||
For example, some-package==1.2 is pinned; some-package>1.2 is not.
|
||||
"""
|
||||
specifiers = self.specifier
|
||||
return len(specifiers) == 1 and next(iter(specifiers)).operator in {"==", "==="}
|
||||
return len(specifiers) == 1 and next(iter(specifiers)).operator in {'==', '==='}
|
||||
|
||||
def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> bool:
|
||||
def match_markers(self, extras_requested: Iterable[str] | None = None) -> bool:
|
||||
if not extras_requested:
|
||||
# Provide an extra to safely evaluate the markers
|
||||
# without matching any extra
|
||||
extras_requested = ("",)
|
||||
extras_requested = ('',)
|
||||
if self.markers is not None:
|
||||
return any(
|
||||
self.markers.evaluate({"extra": extra}) for extra in extras_requested
|
||||
self.markers.evaluate({'extra': extra}) for extra in extras_requested
|
||||
)
|
||||
else:
|
||||
return True
|
||||
|
|
@ -284,7 +289,7 @@ class InstallRequirement:
|
|||
good_hashes.setdefault(link.hash_name, []).append(link.hash)
|
||||
return Hashes(good_hashes)
|
||||
|
||||
def from_path(self) -> Optional[str]:
|
||||
def from_path(self) -> str | None:
|
||||
"""Format a nice indicator to show where this "comes from" """
|
||||
if self.req is None:
|
||||
return None
|
||||
|
|
@ -295,11 +300,11 @@ class InstallRequirement:
|
|||
else:
|
||||
comes_from = self.comes_from.from_path()
|
||||
if comes_from:
|
||||
s += "->" + comes_from
|
||||
s += '->' + comes_from
|
||||
return s
|
||||
|
||||
def ensure_build_location(
|
||||
self, build_dir: str, autodelete: bool, parallel_builds: bool
|
||||
self, build_dir: str, autodelete: bool, parallel_builds: bool,
|
||||
) -> str:
|
||||
assert build_dir is not None
|
||||
if self._temp_build_dir is not None:
|
||||
|
|
@ -310,7 +315,7 @@ class InstallRequirement:
|
|||
# builds (such as numpy). Thus, we ensure that the real path
|
||||
# is returned.
|
||||
self._temp_build_dir = TempDirectory(
|
||||
kind=tempdir_kinds.REQ_BUILD, globally_managed=True
|
||||
kind=tempdir_kinds.REQ_BUILD, globally_managed=True,
|
||||
)
|
||||
|
||||
return self._temp_build_dir.path
|
||||
|
|
@ -323,12 +328,12 @@ class InstallRequirement:
|
|||
# name so multiple builds do not interfere with each other.
|
||||
dir_name: str = canonicalize_name(self.name)
|
||||
if parallel_builds:
|
||||
dir_name = f"{dir_name}_{uuid.uuid4().hex}"
|
||||
dir_name = f'{dir_name}_{uuid.uuid4().hex}'
|
||||
|
||||
# FIXME: Is there a better place to create the build_dir? (hg and bzr
|
||||
# need this)
|
||||
if not os.path.exists(build_dir):
|
||||
logger.debug("Creating directory %s", build_dir)
|
||||
logger.debug('Creating directory %s', build_dir)
|
||||
os.makedirs(build_dir)
|
||||
actual_build_dir = os.path.join(build_dir, dir_name)
|
||||
# `None` indicates that we respect the globally-configured deletion
|
||||
|
|
@ -348,32 +353,32 @@ class InstallRequirement:
|
|||
assert self.source_dir is not None
|
||||
|
||||
# Construct a Requirement object from the generated metadata
|
||||
if isinstance(parse_version(self.metadata["Version"]), Version):
|
||||
op = "=="
|
||||
if isinstance(parse_version(self.metadata['Version']), Version):
|
||||
op = '=='
|
||||
else:
|
||||
op = "==="
|
||||
op = '==='
|
||||
|
||||
self.req = Requirement(
|
||||
"".join(
|
||||
''.join(
|
||||
[
|
||||
self.metadata["Name"],
|
||||
self.metadata['Name'],
|
||||
op,
|
||||
self.metadata["Version"],
|
||||
]
|
||||
)
|
||||
self.metadata['Version'],
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def warn_on_mismatching_name(self) -> None:
|
||||
metadata_name = canonicalize_name(self.metadata["Name"])
|
||||
metadata_name = canonicalize_name(self.metadata['Name'])
|
||||
if canonicalize_name(self.req.name) == metadata_name:
|
||||
# Everything is fine.
|
||||
return
|
||||
|
||||
# If we're here, there's a mismatch. Log a warning about it.
|
||||
logger.warning(
|
||||
"Generating metadata for package %s "
|
||||
"produced metadata for project name %s. Fix your "
|
||||
"#egg=%s fragments.",
|
||||
'Generating metadata for package %s '
|
||||
'produced metadata for project name %s. Fix your '
|
||||
'#egg=%s fragments.',
|
||||
self.name,
|
||||
metadata_name,
|
||||
self.name,
|
||||
|
|
@ -402,9 +407,9 @@ class InstallRequirement:
|
|||
self.should_reinstall = True
|
||||
elif running_under_virtualenv() and existing_dist.in_site_packages:
|
||||
raise InstallationError(
|
||||
f"Will not install to the user site because it will "
|
||||
f"lack sys.path precedence to {existing_dist.raw_name} "
|
||||
f"in {existing_dist.location}"
|
||||
f'Will not install to the user site because it will '
|
||||
f'lack sys.path precedence to {existing_dist.raw_name} '
|
||||
f'in {existing_dist.location}',
|
||||
)
|
||||
else:
|
||||
self.should_reinstall = True
|
||||
|
|
@ -428,26 +433,26 @@ class InstallRequirement:
|
|||
@property
|
||||
def unpacked_source_directory(self) -> str:
|
||||
return os.path.join(
|
||||
self.source_dir, self.link and self.link.subdirectory_fragment or ""
|
||||
self.source_dir, self.link and self.link.subdirectory_fragment or '',
|
||||
)
|
||||
|
||||
@property
|
||||
def setup_py_path(self) -> str:
|
||||
assert self.source_dir, f"No source dir for {self}"
|
||||
setup_py = os.path.join(self.unpacked_source_directory, "setup.py")
|
||||
assert self.source_dir, f'No source dir for {self}'
|
||||
setup_py = os.path.join(self.unpacked_source_directory, 'setup.py')
|
||||
|
||||
return setup_py
|
||||
|
||||
@property
|
||||
def setup_cfg_path(self) -> str:
|
||||
assert self.source_dir, f"No source dir for {self}"
|
||||
setup_cfg = os.path.join(self.unpacked_source_directory, "setup.cfg")
|
||||
assert self.source_dir, f'No source dir for {self}'
|
||||
setup_cfg = os.path.join(self.unpacked_source_directory, 'setup.cfg')
|
||||
|
||||
return setup_cfg
|
||||
|
||||
@property
|
||||
def pyproject_toml_path(self) -> str:
|
||||
assert self.source_dir, f"No source dir for {self}"
|
||||
assert self.source_dir, f'No source dir for {self}'
|
||||
return make_pyproject_path(self.unpacked_source_directory)
|
||||
|
||||
def load_pyproject_toml(self) -> None:
|
||||
|
|
@ -459,7 +464,7 @@ class InstallRequirement:
|
|||
follow the PEP 517 or legacy (setup.py) code path.
|
||||
"""
|
||||
pyproject_toml_data = load_pyproject_toml(
|
||||
self.use_pep517, self.pyproject_toml_path, self.setup_py_path, str(self)
|
||||
self.use_pep517, self.pyproject_toml_path, self.setup_py_path, str(self),
|
||||
)
|
||||
|
||||
if pyproject_toml_data is None:
|
||||
|
|
@ -483,18 +488,18 @@ class InstallRequirement:
|
|||
or as a setup.py or a setup.cfg
|
||||
"""
|
||||
if (
|
||||
self.editable
|
||||
and self.use_pep517
|
||||
and not self.supports_pyproject_editable()
|
||||
and not os.path.isfile(self.setup_py_path)
|
||||
and not os.path.isfile(self.setup_cfg_path)
|
||||
self.editable and
|
||||
self.use_pep517 and
|
||||
not self.supports_pyproject_editable() and
|
||||
not os.path.isfile(self.setup_py_path) and
|
||||
not os.path.isfile(self.setup_cfg_path)
|
||||
):
|
||||
raise InstallationError(
|
||||
f"Project {self} has a 'pyproject.toml' and its build "
|
||||
f"backend is missing the 'build_editable' hook. Since it does not "
|
||||
f"have a 'setup.py' nor a 'setup.cfg', "
|
||||
f"it cannot be installed in editable mode. "
|
||||
f"Consider using a build backend that supports PEP 660."
|
||||
f'it cannot be installed in editable mode. '
|
||||
f'Consider using a build backend that supports PEP 660.',
|
||||
)
|
||||
|
||||
def prepare_metadata(self) -> None:
|
||||
|
|
@ -504,14 +509,14 @@ class InstallRequirement:
|
|||
Under legacy processing, call setup.py egg-info.
|
||||
"""
|
||||
assert self.source_dir
|
||||
details = self.name or f"from {self.link}"
|
||||
details = self.name or f'from {self.link}'
|
||||
|
||||
if self.use_pep517:
|
||||
assert self.pep517_backend is not None
|
||||
if (
|
||||
self.editable
|
||||
and self.permit_editable_wheels
|
||||
and self.supports_pyproject_editable()
|
||||
self.editable and
|
||||
self.permit_editable_wheels and
|
||||
self.supports_pyproject_editable()
|
||||
):
|
||||
self.metadata_directory = generate_editable_metadata(
|
||||
build_env=self.build_env,
|
||||
|
|
@ -543,7 +548,7 @@ class InstallRequirement:
|
|||
|
||||
@property
|
||||
def metadata(self) -> Any:
|
||||
if not hasattr(self, "_metadata"):
|
||||
if not hasattr(self, '_metadata'):
|
||||
self._metadata = self.get_dist().metadata
|
||||
|
||||
return self._metadata
|
||||
|
|
@ -553,16 +558,16 @@ class InstallRequirement:
|
|||
|
||||
def assert_source_matches_version(self) -> None:
|
||||
assert self.source_dir
|
||||
version = self.metadata["version"]
|
||||
version = self.metadata['version']
|
||||
if self.req.specifier and version not in self.req.specifier:
|
||||
logger.warning(
|
||||
"Requested %s, but installing version %s",
|
||||
'Requested %s, but installing version %s',
|
||||
self,
|
||||
version,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"Source in %s has version %s, which satisfies requirement %s",
|
||||
'Source in %s has version %s, which satisfies requirement %s',
|
||||
display_path(self.source_dir),
|
||||
version,
|
||||
self,
|
||||
|
|
@ -595,26 +600,26 @@ class InstallRequirement:
|
|||
def update_editable(self) -> None:
|
||||
if not self.link:
|
||||
logger.debug(
|
||||
"Cannot update repository at %s; repository location is unknown",
|
||||
'Cannot update repository at %s; repository location is unknown',
|
||||
self.source_dir,
|
||||
)
|
||||
return
|
||||
assert self.editable
|
||||
assert self.source_dir
|
||||
if self.link.scheme == "file":
|
||||
if self.link.scheme == 'file':
|
||||
# Static paths don't get updated
|
||||
return
|
||||
vcs_backend = vcs.get_backend_for_scheme(self.link.scheme)
|
||||
# Editable requirements are validated in Requirement constructors.
|
||||
# So here, if it's neither a path nor a valid VCS URL, it's a bug.
|
||||
assert vcs_backend, f"Unsupported VCS URL {self.link.url}"
|
||||
assert vcs_backend, f'Unsupported VCS URL {self.link.url}'
|
||||
hidden_url = hide_url(self.link.url)
|
||||
vcs_backend.obtain(self.source_dir, url=hidden_url, verbosity=0)
|
||||
|
||||
# Top-level Actions
|
||||
def uninstall(
|
||||
self, auto_confirm: bool = False, verbose: bool = False
|
||||
) -> Optional[UninstallPathSet]:
|
||||
self, auto_confirm: bool = False, verbose: bool = False,
|
||||
) -> UninstallPathSet | None:
|
||||
"""
|
||||
Uninstall the distribution currently satisfying this requirement.
|
||||
|
||||
|
|
@ -630,9 +635,9 @@ class InstallRequirement:
|
|||
assert self.req
|
||||
dist = get_default_environment().get_distribution(self.req.name)
|
||||
if not dist:
|
||||
logger.warning("Skipping %s as it is not installed.", self.name)
|
||||
logger.warning('Skipping %s as it is not installed.', self.name)
|
||||
return None
|
||||
logger.info("Found existing installation: %s", dist)
|
||||
logger.info('Found existing installation: %s', dist)
|
||||
|
||||
uninstalled_pathset = UninstallPathSet.from_dist(dist)
|
||||
uninstalled_pathset.remove(auto_confirm, verbose)
|
||||
|
|
@ -641,17 +646,17 @@ class InstallRequirement:
|
|||
def _get_archive_name(self, path: str, parentdir: str, rootdir: str) -> str:
|
||||
def _clean_zip_name(name: str, prefix: str) -> str:
|
||||
assert name.startswith(
|
||||
prefix + os.path.sep
|
||||
prefix + os.path.sep,
|
||||
), f"name {name!r} doesn't start with prefix {prefix!r}"
|
||||
name = name[len(prefix) + 1 :]
|
||||
name = name.replace(os.path.sep, "/")
|
||||
name = name[len(prefix) + 1:]
|
||||
name = name.replace(os.path.sep, '/')
|
||||
return name
|
||||
|
||||
path = os.path.join(parentdir, path)
|
||||
name = _clean_zip_name(path, rootdir)
|
||||
return self.name + "/" + name
|
||||
return self.name + '/' + name
|
||||
|
||||
def archive(self, build_dir: Optional[str]) -> None:
|
||||
def archive(self, build_dir: str | None) -> None:
|
||||
"""Saves archive to provided build_dir.
|
||||
|
||||
Used for saving downloaded VCS requirements as part of `pip download`.
|
||||
|
|
@ -661,29 +666,29 @@ class InstallRequirement:
|
|||
return
|
||||
|
||||
create_archive = True
|
||||
archive_name = "{}-{}.zip".format(self.name, self.metadata["version"])
|
||||
archive_name = '{}-{}.zip'.format(self.name, self.metadata['version'])
|
||||
archive_path = os.path.join(build_dir, archive_name)
|
||||
|
||||
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)),
|
||||
("i", "w", "b", "a"),
|
||||
'The file {} exists. (i)gnore, (w)ipe, '
|
||||
'(b)ackup, (a)bort '.format(display_path(archive_path)),
|
||||
('i', 'w', 'b', 'a'),
|
||||
)
|
||||
if response == "i":
|
||||
if response == 'i':
|
||||
create_archive = False
|
||||
elif response == "w":
|
||||
logger.warning("Deleting %s", display_path(archive_path))
|
||||
elif response == 'w':
|
||||
logger.warning('Deleting %s', display_path(archive_path))
|
||||
os.remove(archive_path)
|
||||
elif response == "b":
|
||||
elif response == 'b':
|
||||
dest_file = backup_dir(archive_path)
|
||||
logger.warning(
|
||||
"Backing up %s to %s",
|
||||
'Backing up %s to %s',
|
||||
display_path(archive_path),
|
||||
display_path(dest_file),
|
||||
)
|
||||
shutil.move(archive_path, dest_file)
|
||||
elif response == "a":
|
||||
elif response == 'a':
|
||||
sys.exit(-1)
|
||||
|
||||
if not create_archive:
|
||||
|
|
@ -691,7 +696,7 @@ class InstallRequirement:
|
|||
|
||||
zip_output = zipfile.ZipFile(
|
||||
archive_path,
|
||||
"w",
|
||||
'w',
|
||||
zipfile.ZIP_DEFLATED,
|
||||
allowZip64=True,
|
||||
)
|
||||
|
|
@ -704,9 +709,9 @@ class InstallRequirement:
|
|||
parentdir=dirpath,
|
||||
rootdir=dir,
|
||||
)
|
||||
zipdir = zipfile.ZipInfo(dir_arcname + "/")
|
||||
zipdir = zipfile.ZipInfo(dir_arcname + '/')
|
||||
zipdir.external_attr = 0x1ED << 16 # 0o755
|
||||
zip_output.writestr(zipdir, "")
|
||||
zip_output.writestr(zipdir, '')
|
||||
for filename in filenames:
|
||||
file_arcname = self._get_archive_name(
|
||||
filename,
|
||||
|
|
@ -716,15 +721,15 @@ class InstallRequirement:
|
|||
filename = os.path.join(dirpath, filename)
|
||||
zip_output.write(filename, file_arcname)
|
||||
|
||||
logger.info("Saved %s", display_path(archive_path))
|
||||
logger.info('Saved %s', display_path(archive_path))
|
||||
|
||||
def install(
|
||||
self,
|
||||
install_options: List[str],
|
||||
global_options: Optional[Sequence[str]] = None,
|
||||
root: Optional[str] = None,
|
||||
home: Optional[str] = None,
|
||||
prefix: Optional[str] = None,
|
||||
install_options: list[str],
|
||||
global_options: Sequence[str] | None = None,
|
||||
root: str | None = None,
|
||||
home: str | None = None,
|
||||
prefix: str | None = None,
|
||||
warn_script_location: bool = True,
|
||||
use_user_site: bool = False,
|
||||
pycompile: bool = True,
|
||||
|
|
@ -819,11 +824,11 @@ class InstallRequirement:
|
|||
deprecated(
|
||||
reason=(
|
||||
"{} was installed using the legacy 'setup.py install' "
|
||||
"method, because a wheel could not be built for it.".format(
|
||||
self.name
|
||||
'method, because a wheel could not be built for it.'.format(
|
||||
self.name,
|
||||
)
|
||||
),
|
||||
replacement="to fix the wheel build issue reported above",
|
||||
replacement='to fix the wheel build issue reported above',
|
||||
gone_in=None,
|
||||
issue=8368,
|
||||
)
|
||||
|
|
@ -832,24 +837,24 @@ class InstallRequirement:
|
|||
def check_invalid_constraint_type(req: InstallRequirement) -> str:
|
||||
|
||||
# Check for unsupported forms
|
||||
problem = ""
|
||||
problem = ''
|
||||
if not req.name:
|
||||
problem = "Unnamed requirements are not allowed as constraints"
|
||||
problem = 'Unnamed requirements are not allowed as constraints'
|
||||
elif req.editable:
|
||||
problem = "Editable requirements are not allowed as constraints"
|
||||
problem = 'Editable requirements are not allowed as constraints'
|
||||
elif req.extras:
|
||||
problem = "Constraints cannot have extras"
|
||||
problem = 'Constraints cannot have extras'
|
||||
|
||||
if problem:
|
||||
deprecated(
|
||||
reason=(
|
||||
"Constraints are only allowed to take the form of a package "
|
||||
"name and a version specifier. Other forms were originally "
|
||||
"permitted as an accident of the implementation, but were "
|
||||
"undocumented. The new implementation of the resolver no "
|
||||
"longer supports these forms."
|
||||
'Constraints are only allowed to take the form of a package '
|
||||
'name and a version specifier. Other forms were originally '
|
||||
'permitted as an accident of the implementation, but were '
|
||||
'undocumented. The new implementation of the resolver no '
|
||||
'longer supports these forms.'
|
||||
),
|
||||
replacement="replacing the constraint with a requirement",
|
||||
replacement='replacing the constraint with a requirement',
|
||||
# No plan yet for when the new resolver becomes default
|
||||
gone_in=None,
|
||||
issue=8210,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from typing import Dict, Iterable, List, Optional, Tuple
|
||||
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.models.wheel import Wheel
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils import compatibility_tags
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -16,29 +21,29 @@ class RequirementSet:
|
|||
def __init__(self, check_supported_wheels: bool = True) -> None:
|
||||
"""Create a RequirementSet."""
|
||||
|
||||
self.requirements: Dict[str, InstallRequirement] = OrderedDict()
|
||||
self.requirements: dict[str, InstallRequirement] = OrderedDict()
|
||||
self.check_supported_wheels = check_supported_wheels
|
||||
|
||||
self.unnamed_requirements: List[InstallRequirement] = []
|
||||
self.unnamed_requirements: list[InstallRequirement] = []
|
||||
|
||||
def __str__(self) -> str:
|
||||
requirements = sorted(
|
||||
(req for req in self.requirements.values() if not req.comes_from),
|
||||
key=lambda req: canonicalize_name(req.name or ""),
|
||||
key=lambda req: canonicalize_name(req.name or ''),
|
||||
)
|
||||
return " ".join(str(req.req) for req in requirements)
|
||||
return ' '.join(str(req.req) for req in requirements)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
requirements = sorted(
|
||||
self.requirements.values(),
|
||||
key=lambda req: canonicalize_name(req.name or ""),
|
||||
key=lambda req: canonicalize_name(req.name or ''),
|
||||
)
|
||||
|
||||
format_string = "<{classname} object; {count} requirement(s): {reqs}>"
|
||||
format_string = '<{classname} object; {count} requirement(s): {reqs}>'
|
||||
return format_string.format(
|
||||
classname=self.__class__.__name__,
|
||||
count=len(requirements),
|
||||
reqs=", ".join(str(req.req) for req in requirements),
|
||||
reqs=', '.join(str(req.req) for req in requirements),
|
||||
)
|
||||
|
||||
def add_unnamed_requirement(self, install_req: InstallRequirement) -> None:
|
||||
|
|
@ -54,9 +59,9 @@ class RequirementSet:
|
|||
def add_requirement(
|
||||
self,
|
||||
install_req: InstallRequirement,
|
||||
parent_req_name: Optional[str] = None,
|
||||
extras_requested: Optional[Iterable[str]] = None,
|
||||
) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
|
||||
parent_req_name: str | None = None,
|
||||
extras_requested: Iterable[str] | None = None,
|
||||
) -> tuple[list[InstallRequirement], InstallRequirement | None]:
|
||||
"""Add install_req as a requirement to install.
|
||||
|
||||
:param parent_req_name: The name of the requirement that needed this
|
||||
|
|
@ -89,9 +94,9 @@ class RequirementSet:
|
|||
tags = compatibility_tags.get_supported()
|
||||
if self.check_supported_wheels and not wheel.supported(tags):
|
||||
raise InstallationError(
|
||||
"{} is not a supported wheel on this platform.".format(
|
||||
wheel.filename
|
||||
)
|
||||
'{} is not a supported wheel on this platform.'.format(
|
||||
wheel.filename,
|
||||
),
|
||||
)
|
||||
|
||||
# This next bit is really a sanity check.
|
||||
|
|
@ -106,26 +111,26 @@ class RequirementSet:
|
|||
return [install_req], None
|
||||
|
||||
try:
|
||||
existing_req: Optional[InstallRequirement] = self.get_requirement(
|
||||
install_req.name
|
||||
existing_req: InstallRequirement | None = self.get_requirement(
|
||||
install_req.name,
|
||||
)
|
||||
except KeyError:
|
||||
existing_req = None
|
||||
|
||||
has_conflicting_requirement = (
|
||||
parent_req_name is None
|
||||
and existing_req
|
||||
and not existing_req.constraint
|
||||
and existing_req.extras == install_req.extras
|
||||
and existing_req.req
|
||||
and install_req.req
|
||||
and existing_req.req.specifier != install_req.req.specifier
|
||||
parent_req_name is None and
|
||||
existing_req and
|
||||
not existing_req.constraint and
|
||||
existing_req.extras == install_req.extras and
|
||||
existing_req.req and
|
||||
install_req.req and
|
||||
existing_req.req.specifier != install_req.req.specifier
|
||||
)
|
||||
if has_conflicting_requirement:
|
||||
raise InstallationError(
|
||||
"Double requirement given: {} (already in {}, name={!r})".format(
|
||||
install_req, existing_req, install_req.name
|
||||
)
|
||||
'Double requirement given: {} (already in {}, name={!r})'.format(
|
||||
install_req, existing_req, install_req.name,
|
||||
),
|
||||
)
|
||||
|
||||
# When no existing requirement exists, add the requirement as a
|
||||
|
|
@ -146,8 +151,8 @@ class RequirementSet:
|
|||
if does_not_satisfy_constraint:
|
||||
raise InstallationError(
|
||||
"Could not satisfy constraints for '{}': "
|
||||
"installation from path or url cannot be "
|
||||
"constrained to a version".format(install_req.name)
|
||||
'installation from path or url cannot be '
|
||||
'constrained to a version'.format(install_req.name),
|
||||
)
|
||||
# If we're now installing a constraint, mark the existing
|
||||
# object for real installation.
|
||||
|
|
@ -157,10 +162,10 @@ class RequirementSet:
|
|||
if install_req.user_supplied:
|
||||
existing_req.user_supplied = True
|
||||
existing_req.extras = tuple(
|
||||
sorted(set(existing_req.extras) | set(install_req.extras))
|
||||
sorted(set(existing_req.extras) | set(install_req.extras)),
|
||||
)
|
||||
logger.debug(
|
||||
"Setting %s extras to: %s",
|
||||
'Setting %s extras to: %s',
|
||||
existing_req,
|
||||
existing_req.extras,
|
||||
)
|
||||
|
|
@ -172,8 +177,8 @@ class RequirementSet:
|
|||
project_name = canonicalize_name(name)
|
||||
|
||||
return (
|
||||
project_name in self.requirements
|
||||
and not self.requirements[project_name].constraint
|
||||
project_name in self.requirements and
|
||||
not self.requirements[project_name].constraint
|
||||
)
|
||||
|
||||
def get_requirement(self, name: str) -> InstallRequirement:
|
||||
|
|
@ -182,8 +187,8 @@ class RequirementSet:
|
|||
if project_name in self.requirements:
|
||||
return self.requirements[project_name]
|
||||
|
||||
raise KeyError(f"No project with the name {name!r}")
|
||||
raise KeyError(f'No project with the name {name!r}')
|
||||
|
||||
@property
|
||||
def all_requirements(self) -> List[InstallRequirement]:
|
||||
def all_requirements(self) -> list[InstallRequirement]:
|
||||
return self.unnamed_requirements + list(self.requirements.values())
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
from types import TracebackType
|
||||
from typing import Dict, Iterator, Optional, Set, Type, Union
|
||||
from typing import Dict
|
||||
from typing import Iterator
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Type
|
||||
from typing import Union
|
||||
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
|
|
@ -18,7 +25,7 @@ def update_env_context_manager(**changes: str) -> Iterator[None]:
|
|||
|
||||
# Save values from the target and change them.
|
||||
non_existent_marker = object()
|
||||
saved_values: Dict[str, Union[object, str]] = {}
|
||||
saved_values: dict[str, object | str] = {}
|
||||
for name, new_value in changes.items():
|
||||
try:
|
||||
saved_values[name] = target[name]
|
||||
|
|
@ -39,13 +46,13 @@ def update_env_context_manager(**changes: str) -> Iterator[None]:
|
|||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def get_requirement_tracker() -> Iterator["RequirementTracker"]:
|
||||
root = os.environ.get("PIP_REQ_TRACKER")
|
||||
def get_requirement_tracker() -> Iterator[RequirementTracker]:
|
||||
root = os.environ.get('PIP_REQ_TRACKER')
|
||||
with contextlib.ExitStack() as ctx:
|
||||
if root is None:
|
||||
root = ctx.enter_context(TempDirectory(kind="req-tracker")).path
|
||||
root = ctx.enter_context(TempDirectory(kind='req-tracker')).path
|
||||
ctx.enter_context(update_env_context_manager(PIP_REQ_TRACKER=root))
|
||||
logger.debug("Initialized build tracking at %s", root)
|
||||
logger.debug('Initialized build tracking at %s', root)
|
||||
|
||||
with RequirementTracker(root) as tracker:
|
||||
yield tracker
|
||||
|
|
@ -54,18 +61,18 @@ def get_requirement_tracker() -> Iterator["RequirementTracker"]:
|
|||
class RequirementTracker:
|
||||
def __init__(self, root: str) -> None:
|
||||
self._root = root
|
||||
self._entries: Set[InstallRequirement] = set()
|
||||
logger.debug("Created build tracker: %s", self._root)
|
||||
self._entries: set[InstallRequirement] = set()
|
||||
logger.debug('Created build tracker: %s', self._root)
|
||||
|
||||
def __enter__(self) -> "RequirementTracker":
|
||||
logger.debug("Entered build tracker: %s", self._root)
|
||||
def __enter__(self) -> RequirementTracker:
|
||||
logger.debug('Entered build tracker: %s', self._root)
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
exc_type: type[BaseException] | None,
|
||||
exc_val: BaseException | None,
|
||||
exc_tb: TracebackType | None,
|
||||
) -> None:
|
||||
self.cleanup()
|
||||
|
||||
|
|
@ -88,18 +95,18 @@ class RequirementTracker:
|
|||
except FileNotFoundError:
|
||||
pass
|
||||
else:
|
||||
message = "{} is already being built: {}".format(req.link, contents)
|
||||
message = f'{req.link} is already being built: {contents}'
|
||||
raise LookupError(message)
|
||||
|
||||
# If we're here, req should really not be building already.
|
||||
assert req not in self._entries
|
||||
|
||||
# Start tracking this requirement.
|
||||
with open(entry_path, "w", encoding="utf-8") as fp:
|
||||
with open(entry_path, 'w', encoding='utf-8') as fp:
|
||||
fp.write(str(req))
|
||||
self._entries.add(req)
|
||||
|
||||
logger.debug("Added %s to build tracker %r", req, self._root)
|
||||
logger.debug('Added %s to build tracker %r', req, self._root)
|
||||
|
||||
def remove(self, req: InstallRequirement) -> None:
|
||||
"""Remove an InstallRequirement from build tracking."""
|
||||
|
|
@ -109,13 +116,13 @@ class RequirementTracker:
|
|||
os.unlink(self._entry_path(req.link))
|
||||
self._entries.remove(req)
|
||||
|
||||
logger.debug("Removed %s from build tracker %r", req, self._root)
|
||||
logger.debug('Removed %s from build tracker %r', req, self._root)
|
||||
|
||||
def cleanup(self) -> None:
|
||||
for req in set(self._entries):
|
||||
self.remove(req)
|
||||
|
||||
logger.debug("Removed build tracker: %r", self._root)
|
||||
logger.debug('Removed build tracker: %r', self._root)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def track(self, req: InstallRequirement) -> Iterator[None]:
|
||||
|
|
|
|||
|
|
@ -1,18 +1,35 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import os
|
||||
import sys
|
||||
import sysconfig
|
||||
from importlib.util import cache_from_source
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.exceptions import UninstallationError
|
||||
from pip._internal.locations import get_bin_prefix, get_bin_user
|
||||
from pip._internal.locations import get_bin_prefix
|
||||
from pip._internal.locations import get_bin_user
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.utils.compat import WINDOWS
|
||||
from pip._internal.utils.egg_link import egg_link_path_from_location
|
||||
from pip._internal.utils.logging import getLogger, indent_log
|
||||
from pip._internal.utils.misc import ask, is_local, normalize_path, renames, rmtree
|
||||
from pip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory
|
||||
from pip._internal.utils.logging import getLogger
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import ask
|
||||
from pip._internal.utils.misc import is_local
|
||||
from pip._internal.utils.misc import normalize_path
|
||||
from pip._internal.utils.misc import renames
|
||||
from pip._internal.utils.misc import rmtree
|
||||
from pip._internal.utils.temp_dir import AdjacentTempDirectory
|
||||
from pip._internal.utils.temp_dir import TempDirectory
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
|
@ -26,18 +43,18 @@ def _script_names(bin_dir: str, script_name: str, is_gui: bool) -> Iterator[str]
|
|||
yield exe_name
|
||||
if not WINDOWS:
|
||||
return
|
||||
yield f"{exe_name}.exe"
|
||||
yield f"{exe_name}.exe.manifest"
|
||||
yield f'{exe_name}.exe'
|
||||
yield f'{exe_name}.exe.manifest'
|
||||
if is_gui:
|
||||
yield f"{exe_name}-script.pyw"
|
||||
yield f'{exe_name}-script.pyw'
|
||||
else:
|
||||
yield f"{exe_name}-script.py"
|
||||
yield f'{exe_name}-script.py'
|
||||
|
||||
|
||||
def _unique(fn: Callable[..., Iterator[Any]]) -> Callable[..., Iterator[Any]]:
|
||||
@functools.wraps(fn)
|
||||
def unique(*args: Any, **kw: Any) -> Iterator[Any]:
|
||||
seen: Set[Any] = set()
|
||||
seen: set[Any] = set()
|
||||
for item in fn(*args, **kw):
|
||||
if item not in seen:
|
||||
seen.add(item)
|
||||
|
|
@ -62,46 +79,46 @@ def uninstallation_paths(dist: BaseDistribution) -> Iterator[str]:
|
|||
https://packaging.python.org/specifications/recording-installed-packages/
|
||||
"""
|
||||
location = dist.location
|
||||
assert location is not None, "not installed"
|
||||
assert location is not None, 'not installed'
|
||||
|
||||
entries = dist.iter_declared_entries()
|
||||
if entries is None:
|
||||
msg = "Cannot uninstall {dist}, RECORD file not found.".format(dist=dist)
|
||||
msg = f'Cannot uninstall {dist}, RECORD file not found.'
|
||||
installer = dist.installer
|
||||
if not installer or installer == "pip":
|
||||
dep = "{}=={}".format(dist.raw_name, dist.version)
|
||||
if not installer or installer == 'pip':
|
||||
dep = f'{dist.raw_name}=={dist.version}'
|
||||
msg += (
|
||||
" You might be able to recover from this via: "
|
||||
' 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)
|
||||
msg += f' Hint: The package was installed by {installer}.'
|
||||
raise UninstallationError(msg)
|
||||
|
||||
for entry in entries:
|
||||
path = os.path.join(location, entry)
|
||||
yield path
|
||||
if path.endswith(".py"):
|
||||
if path.endswith('.py'):
|
||||
dn, fn = os.path.split(path)
|
||||
base = fn[:-3]
|
||||
path = os.path.join(dn, base + ".pyc")
|
||||
path = os.path.join(dn, base + '.pyc')
|
||||
yield path
|
||||
path = os.path.join(dn, base + ".pyo")
|
||||
path = os.path.join(dn, base + '.pyo')
|
||||
yield path
|
||||
|
||||
|
||||
def compact(paths: Iterable[str]) -> Set[str]:
|
||||
def compact(paths: Iterable[str]) -> set[str]:
|
||||
"""Compact a path set to contain the minimal number of paths
|
||||
necessary to contain all paths in the set. If /a/path/ and
|
||||
/a/path/to/a/file.txt are both in the set, leave only the
|
||||
shorter path."""
|
||||
|
||||
sep = os.path.sep
|
||||
short_paths: Set[str] = set()
|
||||
short_paths: set[str] = set()
|
||||
for path in sorted(paths, key=len):
|
||||
should_skip = any(
|
||||
path.startswith(shortpath.rstrip("*"))
|
||||
and path[len(shortpath.rstrip("*").rstrip(sep))] == sep
|
||||
path.startswith(shortpath.rstrip('*')) and
|
||||
path[len(shortpath.rstrip('*').rstrip(sep))] == sep
|
||||
for shortpath in short_paths
|
||||
)
|
||||
if not should_skip:
|
||||
|
|
@ -109,7 +126,7 @@ def compact(paths: Iterable[str]) -> Set[str]:
|
|||
return short_paths
|
||||
|
||||
|
||||
def compress_for_rename(paths: Iterable[str]) -> Set[str]:
|
||||
def compress_for_rename(paths: Iterable[str]) -> set[str]:
|
||||
"""Returns a set containing the paths that need to be renamed.
|
||||
|
||||
This set may include directories when the original sequence of paths
|
||||
|
|
@ -118,7 +135,7 @@ def compress_for_rename(paths: Iterable[str]) -> Set[str]:
|
|||
case_map = {os.path.normcase(p): p for p in paths}
|
||||
remaining = set(case_map)
|
||||
unchecked = sorted({os.path.split(p)[0] for p in case_map.values()}, key=len)
|
||||
wildcards: Set[str] = set()
|
||||
wildcards: set[str] = set()
|
||||
|
||||
def norm_join(*a: str) -> str:
|
||||
return os.path.normcase(os.path.join(*a))
|
||||
|
|
@ -128,8 +145,8 @@ def compress_for_rename(paths: Iterable[str]) -> Set[str]:
|
|||
# This directory has already been handled.
|
||||
continue
|
||||
|
||||
all_files: Set[str] = set()
|
||||
all_subdirs: Set[str] = set()
|
||||
all_files: set[str] = set()
|
||||
all_subdirs: set[str] = set()
|
||||
for dirname, subdirs, files in os.walk(root):
|
||||
all_subdirs.update(norm_join(root, dirname, d) for d in subdirs)
|
||||
all_files.update(norm_join(root, dirname, f) for f in files)
|
||||
|
|
@ -143,7 +160,7 @@ def compress_for_rename(paths: Iterable[str]) -> Set[str]:
|
|||
return set(map(case_map.__getitem__, remaining)) | wildcards
|
||||
|
||||
|
||||
def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str]]:
|
||||
def compress_for_output_listing(paths: Iterable[str]) -> tuple[set[str], set[str]]:
|
||||
"""Returns a tuple of 2 sets of which paths to display to user
|
||||
|
||||
The first set contains paths that would be deleted. Files of a package
|
||||
|
|
@ -161,9 +178,9 @@ def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str
|
|||
folders = set()
|
||||
files = set()
|
||||
for path in will_remove:
|
||||
if path.endswith(".pyc"):
|
||||
if path.endswith('.pyc'):
|
||||
continue
|
||||
if path.endswith("__init__.py") or ".dist-info" in path:
|
||||
if path.endswith('__init__.py') or '.dist-info' in path:
|
||||
folders.add(os.path.dirname(path))
|
||||
files.add(path)
|
||||
|
||||
|
|
@ -177,18 +194,18 @@ def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str
|
|||
for folder in folders:
|
||||
for dirpath, _, dirfiles in os.walk(folder):
|
||||
for fname in dirfiles:
|
||||
if fname.endswith(".pyc"):
|
||||
if fname.endswith('.pyc'):
|
||||
continue
|
||||
|
||||
file_ = os.path.join(dirpath, fname)
|
||||
if (
|
||||
os.path.isfile(file_)
|
||||
and os.path.normcase(file_) not in _normcased_files
|
||||
os.path.isfile(file_) and
|
||||
os.path.normcase(file_) not in _normcased_files
|
||||
):
|
||||
# We are skipping this file. Add it to the set.
|
||||
will_skip.add(file_)
|
||||
|
||||
will_remove = files | {os.path.join(folder, "*") for folder in folders}
|
||||
will_remove = files | {os.path.join(folder, '*') for folder in folders}
|
||||
|
||||
return will_remove, will_skip
|
||||
|
||||
|
|
@ -200,10 +217,10 @@ class StashedUninstallPathSet:
|
|||
def __init__(self) -> None:
|
||||
# Mapping from source file root to [Adjacent]TempDirectory
|
||||
# for files under that directory.
|
||||
self._save_dirs: Dict[str, TempDirectory] = {}
|
||||
self._save_dirs: dict[str, TempDirectory] = {}
|
||||
# (old path, new path) tuples for each move that may need
|
||||
# to be undone.
|
||||
self._moves: List[Tuple[str, str]] = []
|
||||
self._moves: list[tuple[str, str]] = []
|
||||
|
||||
def _get_directory_stash(self, path: str) -> str:
|
||||
"""Stashes a directory.
|
||||
|
|
@ -214,7 +231,7 @@ class StashedUninstallPathSet:
|
|||
try:
|
||||
save_dir: TempDirectory = AdjacentTempDirectory(path)
|
||||
except OSError:
|
||||
save_dir = TempDirectory(kind="uninstall")
|
||||
save_dir = TempDirectory(kind='uninstall')
|
||||
self._save_dirs[os.path.normcase(path)] = save_dir
|
||||
|
||||
return save_dir.path
|
||||
|
|
@ -238,7 +255,7 @@ class StashedUninstallPathSet:
|
|||
else:
|
||||
# Did not find any suitable root
|
||||
head = os.path.dirname(path)
|
||||
save_dir = TempDirectory(kind="uninstall")
|
||||
save_dir = TempDirectory(kind='uninstall')
|
||||
self._save_dirs[head] = save_dir
|
||||
|
||||
relpath = os.path.relpath(path, head)
|
||||
|
|
@ -277,19 +294,19 @@ class StashedUninstallPathSet:
|
|||
def rollback(self) -> None:
|
||||
"""Undoes the uninstall by moving stashed files back."""
|
||||
for p in self._moves:
|
||||
logger.info("Moving to %s\n from %s", *p)
|
||||
logger.info('Moving to %s\n from %s', *p)
|
||||
|
||||
for new_path, path in self._moves:
|
||||
try:
|
||||
logger.debug("Replacing %s from %s", new_path, path)
|
||||
logger.debug('Replacing %s from %s', new_path, path)
|
||||
if os.path.isfile(new_path) or os.path.islink(new_path):
|
||||
os.unlink(new_path)
|
||||
elif os.path.isdir(new_path):
|
||||
rmtree(new_path)
|
||||
renames(path, new_path)
|
||||
except OSError as ex:
|
||||
logger.error("Failed to restore %s", new_path)
|
||||
logger.debug("Exception: %s", ex)
|
||||
logger.error('Failed to restore %s', new_path)
|
||||
logger.debug('Exception: %s', ex)
|
||||
|
||||
self.commit()
|
||||
|
||||
|
|
@ -303,9 +320,9 @@ class UninstallPathSet:
|
|||
requirement."""
|
||||
|
||||
def __init__(self, dist: BaseDistribution) -> None:
|
||||
self._paths: Set[str] = set()
|
||||
self._refuse: Set[str] = set()
|
||||
self._pth: Dict[str, UninstallPthEntries] = {}
|
||||
self._paths: set[str] = set()
|
||||
self._refuse: set[str] = set()
|
||||
self._pth: dict[str, UninstallPthEntries] = {}
|
||||
self._dist = dist
|
||||
self._moved_paths = StashedUninstallPathSet()
|
||||
|
||||
|
|
@ -333,7 +350,7 @@ class UninstallPathSet:
|
|||
|
||||
# __pycache__ files can show up after 'installed-files.txt' is created,
|
||||
# due to imports
|
||||
if os.path.splitext(path)[1] == ".py":
|
||||
if os.path.splitext(path)[1] == '.py':
|
||||
self.add(cache_from_source(path))
|
||||
|
||||
def add_pth(self, pth_file: str, entry: str) -> None:
|
||||
|
|
@ -356,8 +373,8 @@ class UninstallPathSet:
|
|||
)
|
||||
return
|
||||
|
||||
dist_name_version = f"{self._dist.raw_name}-{self._dist.version}"
|
||||
logger.info("Uninstalling %s:", dist_name_version)
|
||||
dist_name_version = f'{self._dist.raw_name}-{self._dist.version}'
|
||||
logger.info('Uninstalling %s:', dist_name_version)
|
||||
|
||||
with indent_log():
|
||||
if auto_confirm or self._allowed_to_proceed(verbose):
|
||||
|
|
@ -367,12 +384,12 @@ class UninstallPathSet:
|
|||
|
||||
for path in sorted(compact(for_rename)):
|
||||
moved.stash(path)
|
||||
logger.verbose("Removing file or directory %s", path)
|
||||
logger.verbose('Removing file or directory %s', path)
|
||||
|
||||
for pth in self._pth.values():
|
||||
pth.remove()
|
||||
|
||||
logger.info("Successfully uninstalled %s", dist_name_version)
|
||||
logger.info('Successfully uninstalled %s', dist_name_version)
|
||||
|
||||
def _allowed_to_proceed(self, verbose: bool) -> bool:
|
||||
"""Display which files would be deleted and prompt for confirmation"""
|
||||
|
|
@ -394,13 +411,13 @@ class UninstallPathSet:
|
|||
will_remove = set(self._paths)
|
||||
will_skip = set()
|
||||
|
||||
_display("Would remove:", will_remove)
|
||||
_display("Would not remove (might be manually added):", will_skip)
|
||||
_display("Would not remove (outside of prefix):", self._refuse)
|
||||
_display('Would remove:', will_remove)
|
||||
_display('Would not remove (might be manually added):', will_skip)
|
||||
_display('Would not remove (outside of prefix):', self._refuse)
|
||||
if verbose:
|
||||
_display("Will actually move:", compress_for_rename(self._paths))
|
||||
_display('Will actually move:', compress_for_rename(self._paths))
|
||||
|
||||
return ask("Proceed (Y/n)? ", ("y", "n", "")) != "n"
|
||||
return ask('Proceed (Y/n)? ', ('y', 'n', '')) != 'n'
|
||||
|
||||
def rollback(self) -> None:
|
||||
"""Rollback the changes previously made by remove()."""
|
||||
|
|
@ -410,7 +427,7 @@ class UninstallPathSet:
|
|||
self._dist.raw_name,
|
||||
)
|
||||
return
|
||||
logger.info("Rolling back uninstall of %s", self._dist.raw_name)
|
||||
logger.info('Rolling back uninstall of %s', self._dist.raw_name)
|
||||
self._moved_paths.rollback()
|
||||
for pth in self._pth.values():
|
||||
pth.rollback()
|
||||
|
|
@ -420,12 +437,12 @@ class UninstallPathSet:
|
|||
self._moved_paths.commit()
|
||||
|
||||
@classmethod
|
||||
def from_dist(cls, dist: BaseDistribution) -> "UninstallPathSet":
|
||||
def from_dist(cls, dist: BaseDistribution) -> UninstallPathSet:
|
||||
dist_location = dist.location
|
||||
info_location = dist.info_location
|
||||
if dist_location is None:
|
||||
logger.info(
|
||||
"Not uninstalling %s since it is not installed",
|
||||
'Not uninstalling %s since it is not installed',
|
||||
dist.canonical_name,
|
||||
)
|
||||
return cls(dist)
|
||||
|
|
@ -433,7 +450,7 @@ class UninstallPathSet:
|
|||
normalized_dist_location = normalize_path(dist_location)
|
||||
if not dist.local:
|
||||
logger.info(
|
||||
"Not uninstalling %s at %s, outside environment %s",
|
||||
'Not uninstalling %s at %s, outside environment %s',
|
||||
dist.canonical_name,
|
||||
normalized_dist_location,
|
||||
sys.prefix,
|
||||
|
|
@ -442,11 +459,11 @@ class UninstallPathSet:
|
|||
|
||||
if normalized_dist_location in {
|
||||
p
|
||||
for p in {sysconfig.get_path("stdlib"), sysconfig.get_path("platstdlib")}
|
||||
for p in {sysconfig.get_path('stdlib'), sysconfig.get_path('platstdlib')}
|
||||
if p
|
||||
}:
|
||||
logger.info(
|
||||
"Not uninstalling %s at %s, as it is in the standard library.",
|
||||
'Not uninstalling %s at %s, as it is in the standard library.',
|
||||
dist.canonical_name,
|
||||
normalized_dist_location,
|
||||
)
|
||||
|
|
@ -459,12 +476,12 @@ class UninstallPathSet:
|
|||
# directory. This means it is not a modern .dist-info installation, an
|
||||
# egg, or legacy editable.
|
||||
setuptools_flat_installation = (
|
||||
dist.installed_with_setuptools_egg_info
|
||||
and info_location is not None
|
||||
and os.path.exists(info_location)
|
||||
dist.installed_with_setuptools_egg_info and
|
||||
info_location is not None and
|
||||
os.path.exists(info_location) and
|
||||
# If dist is editable and the location points to a ``.egg-info``,
|
||||
# we are in fact in the legacy editable case.
|
||||
and not info_location.endswith(f"{dist.setuptools_filename}.egg-info")
|
||||
not info_location.endswith(f'{dist.setuptools_filename}.egg-info')
|
||||
)
|
||||
|
||||
# Uninstall cases order do matter as in the case of 2 installs of the
|
||||
|
|
@ -479,31 +496,31 @@ class UninstallPathSet:
|
|||
# FIXME: need a test for this elif block
|
||||
# occurs with --single-version-externally-managed/--record outside
|
||||
# of pip
|
||||
elif dist.is_file("top_level.txt"):
|
||||
elif dist.is_file('top_level.txt'):
|
||||
try:
|
||||
namespace_packages = dist.read_text("namespace_packages.txt")
|
||||
namespace_packages = dist.read_text('namespace_packages.txt')
|
||||
except FileNotFoundError:
|
||||
namespaces = []
|
||||
else:
|
||||
namespaces = namespace_packages.splitlines(keepends=False)
|
||||
for top_level_pkg in [
|
||||
p
|
||||
for p in dist.read_text("top_level.txt").splitlines()
|
||||
for p in dist.read_text('top_level.txt').splitlines()
|
||||
if p and p not in namespaces
|
||||
]:
|
||||
path = os.path.join(dist_location, top_level_pkg)
|
||||
paths_to_remove.add(path)
|
||||
paths_to_remove.add(f"{path}.py")
|
||||
paths_to_remove.add(f"{path}.pyc")
|
||||
paths_to_remove.add(f"{path}.pyo")
|
||||
paths_to_remove.add(f'{path}.py')
|
||||
paths_to_remove.add(f'{path}.pyc')
|
||||
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(
|
||||
'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,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
elif dist.installed_as_egg:
|
||||
|
|
@ -514,9 +531,9 @@ class UninstallPathSet:
|
|||
easy_install_egg = os.path.split(dist_location)[1]
|
||||
easy_install_pth = os.path.join(
|
||||
os.path.dirname(dist_location),
|
||||
"easy-install.pth",
|
||||
'easy-install.pth',
|
||||
)
|
||||
paths_to_remove.add_pth(easy_install_pth, "./" + easy_install_egg)
|
||||
paths_to_remove.add_pth(easy_install_pth, './' + easy_install_egg)
|
||||
|
||||
elif dist.installed_with_dist_info:
|
||||
for path in uninstallation_paths(dist):
|
||||
|
|
@ -528,18 +545,18 @@ class UninstallPathSet:
|
|||
with open(develop_egg_link) as fh:
|
||||
link_pointer = os.path.normcase(fh.readline().strip())
|
||||
assert link_pointer == dist_location, (
|
||||
f"Egg-link {link_pointer} does not match installed location of "
|
||||
f"{dist.raw_name} (at {dist_location})"
|
||||
f'Egg-link {link_pointer} does not match installed location of '
|
||||
f'{dist.raw_name} (at {dist_location})'
|
||||
)
|
||||
paths_to_remove.add(develop_egg_link)
|
||||
easy_install_pth = os.path.join(
|
||||
os.path.dirname(develop_egg_link), "easy-install.pth"
|
||||
os.path.dirname(develop_egg_link), 'easy-install.pth',
|
||||
)
|
||||
paths_to_remove.add_pth(easy_install_pth, dist_location)
|
||||
|
||||
else:
|
||||
logger.debug(
|
||||
"Not sure how to uninstall: %s - Check: %s",
|
||||
'Not sure how to uninstall: %s - Check: %s',
|
||||
dist,
|
||||
dist_location,
|
||||
)
|
||||
|
|
@ -551,10 +568,10 @@ class UninstallPathSet:
|
|||
|
||||
# find distutils scripts= scripts
|
||||
try:
|
||||
for script in dist.iterdir("scripts"):
|
||||
for script in dist.iterdir('scripts'):
|
||||
paths_to_remove.add(os.path.join(bin_dir, script.name))
|
||||
if WINDOWS:
|
||||
paths_to_remove.add(os.path.join(bin_dir, f"{script.name}.bat"))
|
||||
paths_to_remove.add(os.path.join(bin_dir, f'{script.name}.bat'))
|
||||
except (FileNotFoundError, NotADirectoryError):
|
||||
pass
|
||||
|
||||
|
|
@ -564,9 +581,9 @@ class UninstallPathSet:
|
|||
bin_dir: str,
|
||||
) -> Iterator[str]:
|
||||
for entry_point in dist.iter_entry_points():
|
||||
if entry_point.group == "console_scripts":
|
||||
if entry_point.group == 'console_scripts':
|
||||
yield from _script_names(bin_dir, entry_point.name, False)
|
||||
elif entry_point.group == "gui_scripts":
|
||||
elif entry_point.group == 'gui_scripts':
|
||||
yield from _script_names(bin_dir, entry_point.name, True)
|
||||
|
||||
for s in iter_scripts_to_remove(dist, bin_dir):
|
||||
|
|
@ -578,8 +595,8 @@ class UninstallPathSet:
|
|||
class UninstallPthEntries:
|
||||
def __init__(self, pth_file: str) -> None:
|
||||
self.file = pth_file
|
||||
self.entries: Set[str] = set()
|
||||
self._saved_lines: Optional[List[bytes]] = None
|
||||
self.entries: set[str] = set()
|
||||
self._saved_lines: list[bytes] | None = None
|
||||
|
||||
def add(self, entry: str) -> None:
|
||||
entry = os.path.normcase(entry)
|
||||
|
|
@ -593,41 +610,41 @@ class UninstallPthEntries:
|
|||
# have more than "\\sever\share". Valid examples: "\\server\share\" or
|
||||
# "\\server\share\folder".
|
||||
if WINDOWS and not os.path.splitdrive(entry)[0]:
|
||||
entry = entry.replace("\\", "/")
|
||||
entry = entry.replace('\\', '/')
|
||||
self.entries.add(entry)
|
||||
|
||||
def remove(self) -> None:
|
||||
logger.verbose("Removing pth entries from %s:", self.file)
|
||||
logger.verbose('Removing pth entries from %s:', self.file)
|
||||
|
||||
# If the file doesn't exist, log a warning and return
|
||||
if not os.path.isfile(self.file):
|
||||
logger.warning("Cannot remove entries from nonexistent file %s", self.file)
|
||||
logger.warning('Cannot remove entries from nonexistent file %s', self.file)
|
||||
return
|
||||
with open(self.file, "rb") as fh:
|
||||
with open(self.file, 'rb') as fh:
|
||||
# windows uses '\r\n' with py3k, but uses '\n' with py2.x
|
||||
lines = fh.readlines()
|
||||
self._saved_lines = lines
|
||||
if any(b"\r\n" in line for line in lines):
|
||||
endline = "\r\n"
|
||||
if any(b'\r\n' in line for line in lines):
|
||||
endline = '\r\n'
|
||||
else:
|
||||
endline = "\n"
|
||||
endline = '\n'
|
||||
# handle missing trailing newline
|
||||
if lines and not lines[-1].endswith(endline.encode("utf-8")):
|
||||
lines[-1] = lines[-1] + endline.encode("utf-8")
|
||||
if lines and not lines[-1].endswith(endline.encode('utf-8')):
|
||||
lines[-1] = lines[-1] + endline.encode('utf-8')
|
||||
for entry in self.entries:
|
||||
try:
|
||||
logger.verbose("Removing entry: %s", entry)
|
||||
lines.remove((entry + endline).encode("utf-8"))
|
||||
logger.verbose('Removing entry: %s', entry)
|
||||
lines.remove((entry + endline).encode('utf-8'))
|
||||
except ValueError:
|
||||
pass
|
||||
with open(self.file, "wb") as fh:
|
||||
with open(self.file, 'wb') as fh:
|
||||
fh.writelines(lines)
|
||||
|
||||
def rollback(self) -> bool:
|
||||
if self._saved_lines is None:
|
||||
logger.error("Cannot roll back changes to %s, none were made", self.file)
|
||||
logger.error('Cannot roll back changes to %s, none were made', self.file)
|
||||
return False
|
||||
logger.debug("Rolling %s back to previous state", self.file)
|
||||
with open(self.file, "wb") as fh:
|
||||
logger.debug('Rolling %s back to previous state', self.file)
|
||||
with open(self.file, 'wb') as fh:
|
||||
fh.writelines(self._saved_lines)
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
from typing import Callable, List, Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.req.req_set import RequirementSet
|
||||
|
||||
InstallRequirementProvider = Callable[
|
||||
[str, Optional[InstallRequirement]], InstallRequirement
|
||||
[str, Optional[InstallRequirement]], InstallRequirement,
|
||||
]
|
||||
|
||||
|
||||
class BaseResolver:
|
||||
def resolve(
|
||||
self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
|
||||
self, root_reqs: list[InstallRequirement], check_supported_wheels: bool,
|
||||
) -> RequirementSet:
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_installation_order(
|
||||
self, req_set: RequirementSet
|
||||
) -> List[InstallRequirement]:
|
||||
self, req_set: RequirementSet,
|
||||
) -> list[InstallRequirement]:
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -9,42 +9,43 @@ for top-level requirements:
|
|||
for sub-dependencies
|
||||
a. "first found, wins" (where the order is breadth first)
|
||||
"""
|
||||
|
||||
# The following comment should be removed at some point in the future.
|
||||
# mypy: strict-optional=False
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
from typing import DefaultDict, Iterable, List, Optional, Set, Tuple
|
||||
|
||||
from pip._vendor.packaging import specifiers
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
from typing import DefaultDict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
|
||||
from pip._internal.cache import WheelCache
|
||||
from pip._internal.exceptions import (
|
||||
BestVersionAlreadyInstalled,
|
||||
DistributionNotFound,
|
||||
HashError,
|
||||
HashErrors,
|
||||
NoneMetadataError,
|
||||
UnsupportedPythonVersion,
|
||||
)
|
||||
from pip._internal.exceptions import BestVersionAlreadyInstalled
|
||||
from pip._internal.exceptions import DistributionNotFound
|
||||
from pip._internal.exceptions import HashError
|
||||
from pip._internal.exceptions import HashErrors
|
||||
from pip._internal.exceptions import NoneMetadataError
|
||||
from pip._internal.exceptions import UnsupportedPythonVersion
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.operations.prepare import RequirementPreparer
|
||||
from pip._internal.req.req_install import (
|
||||
InstallRequirement,
|
||||
check_invalid_constraint_type,
|
||||
)
|
||||
from pip._internal.req.req_install import check_invalid_constraint_type
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.req.req_set import RequirementSet
|
||||
from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider
|
||||
from pip._internal.resolution.base import BaseResolver
|
||||
from pip._internal.resolution.base import InstallRequirementProvider
|
||||
from pip._internal.utils.compatibility_tags import get_supported
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import normalize_version_info
|
||||
from pip._internal.utils.packaging import check_requires_python
|
||||
from pip._vendor.packaging import specifiers
|
||||
from pip._vendor.packaging.requirements import Requirement
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -53,7 +54,7 @@ DiscoveredDependencies = DefaultDict[str, List[InstallRequirement]]
|
|||
|
||||
def _check_dist_requires_python(
|
||||
dist: BaseDistribution,
|
||||
version_info: Tuple[int, int, int],
|
||||
version_info: tuple[int, int, int],
|
||||
ignore_requires_python: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -82,17 +83,17 @@ def _check_dist_requires_python(
|
|||
)
|
||||
except specifiers.InvalidSpecifier as exc:
|
||||
logger.warning(
|
||||
"Package %r has an invalid Requires-Python: %s", dist.raw_name, exc
|
||||
'Package %r has an invalid Requires-Python: %s', dist.raw_name, exc,
|
||||
)
|
||||
return
|
||||
|
||||
if is_compatible:
|
||||
return
|
||||
|
||||
version = ".".join(map(str, version_info))
|
||||
version = '.'.join(map(str, version_info))
|
||||
if ignore_requires_python:
|
||||
logger.debug(
|
||||
"Ignoring failed Requires-Python check for package %r: %s not in %r",
|
||||
'Ignoring failed Requires-Python check for package %r: %s not in %r',
|
||||
dist.raw_name,
|
||||
version,
|
||||
requires_python,
|
||||
|
|
@ -100,9 +101,9 @@ def _check_dist_requires_python(
|
|||
return
|
||||
|
||||
raise UnsupportedPythonVersion(
|
||||
"Package {!r} requires a different Python: {} not in {!r}".format(
|
||||
dist.raw_name, version, requires_python
|
||||
)
|
||||
'Package {!r} requires a different Python: {} not in {!r}'.format(
|
||||
dist.raw_name, version, requires_python,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -111,13 +112,13 @@ class Resolver(BaseResolver):
|
|||
the requested operation without breaking the requirements of any package.
|
||||
"""
|
||||
|
||||
_allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"}
|
||||
_allowed_strategies = {'eager', 'only-if-needed', 'to-satisfy-only'}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
preparer: RequirementPreparer,
|
||||
finder: PackageFinder,
|
||||
wheel_cache: Optional[WheelCache],
|
||||
wheel_cache: WheelCache | None,
|
||||
make_install_req: InstallRequirementProvider,
|
||||
use_user_site: bool,
|
||||
ignore_dependencies: bool,
|
||||
|
|
@ -125,7 +126,7 @@ class Resolver(BaseResolver):
|
|||
ignore_requires_python: bool,
|
||||
force_reinstall: bool,
|
||||
upgrade_strategy: str,
|
||||
py_version_info: Optional[Tuple[int, ...]] = None,
|
||||
py_version_info: tuple[int, ...] | None = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
assert upgrade_strategy in self._allowed_strategies
|
||||
|
|
@ -152,7 +153,7 @@ class Resolver(BaseResolver):
|
|||
self._discovered_dependencies: DiscoveredDependencies = defaultdict(list)
|
||||
|
||||
def resolve(
|
||||
self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
|
||||
self, root_reqs: list[InstallRequirement], check_supported_wheels: bool,
|
||||
) -> RequirementSet:
|
||||
"""Resolve what operations need to be done
|
||||
|
||||
|
|
@ -174,7 +175,7 @@ class Resolver(BaseResolver):
|
|||
# exceptions cannot be checked ahead of time, because
|
||||
# _populate_link() needs to be called before we can make decisions
|
||||
# based on link type.
|
||||
discovered_reqs: List[InstallRequirement] = []
|
||||
discovered_reqs: list[InstallRequirement] = []
|
||||
hash_errors = HashErrors()
|
||||
for req in chain(requirement_set.all_requirements, discovered_reqs):
|
||||
try:
|
||||
|
|
@ -189,12 +190,12 @@ class Resolver(BaseResolver):
|
|||
return requirement_set
|
||||
|
||||
def _is_upgrade_allowed(self, req: InstallRequirement) -> bool:
|
||||
if self.upgrade_strategy == "to-satisfy-only":
|
||||
if self.upgrade_strategy == 'to-satisfy-only':
|
||||
return False
|
||||
elif self.upgrade_strategy == "eager":
|
||||
elif self.upgrade_strategy == 'eager':
|
||||
return True
|
||||
else:
|
||||
assert self.upgrade_strategy == "only-if-needed"
|
||||
assert self.upgrade_strategy == 'only-if-needed'
|
||||
return req.user_supplied or req.constraint
|
||||
|
||||
def _set_req_to_reinstall(self, req: InstallRequirement) -> None:
|
||||
|
|
@ -208,8 +209,8 @@ class Resolver(BaseResolver):
|
|||
req.satisfied_by = None
|
||||
|
||||
def _check_skip_installed(
|
||||
self, req_to_install: InstallRequirement
|
||||
) -> Optional[str]:
|
||||
self, req_to_install: InstallRequirement,
|
||||
) -> str | None:
|
||||
"""Check if req_to_install should be skipped.
|
||||
|
||||
This will check if the req is installed, and whether we should upgrade
|
||||
|
|
@ -239,9 +240,9 @@ class Resolver(BaseResolver):
|
|||
return None
|
||||
|
||||
if not self._is_upgrade_allowed(req_to_install):
|
||||
if self.upgrade_strategy == "only-if-needed":
|
||||
return "already satisfied, skipping upgrade"
|
||||
return "already satisfied"
|
||||
if self.upgrade_strategy == 'only-if-needed':
|
||||
return 'already satisfied, skipping upgrade'
|
||||
return 'already satisfied'
|
||||
|
||||
# Check for the possibility of an upgrade. For link-based
|
||||
# requirements we have to pull the tree down and inspect to assess
|
||||
|
|
@ -251,7 +252,7 @@ class Resolver(BaseResolver):
|
|||
self.finder.find_requirement(req_to_install, upgrade=True)
|
||||
except BestVersionAlreadyInstalled:
|
||||
# Then the best version is installed.
|
||||
return "already up-to-date"
|
||||
return 'already up-to-date'
|
||||
except DistributionNotFound:
|
||||
# No distribution found, so we squash the error. It will
|
||||
# be raised later when we re-try later to do the install.
|
||||
|
|
@ -261,7 +262,7 @@ class Resolver(BaseResolver):
|
|||
self._set_req_to_reinstall(req_to_install)
|
||||
return None
|
||||
|
||||
def _find_requirement_link(self, req: InstallRequirement) -> Optional[Link]:
|
||||
def _find_requirement_link(self, req: InstallRequirement) -> Link | None:
|
||||
upgrade = self._is_upgrade_allowed(req)
|
||||
best_candidate = self.finder.find_requirement(req, upgrade)
|
||||
if not best_candidate:
|
||||
|
|
@ -270,14 +271,14 @@ class Resolver(BaseResolver):
|
|||
# Log a warning per PEP 592 if necessary before returning.
|
||||
link = best_candidate.link
|
||||
if link.is_yanked:
|
||||
reason = link.yanked_reason or "<none given>"
|
||||
reason = link.yanked_reason or '<none given>'
|
||||
msg = (
|
||||
# Mark this as a unicode string to prevent
|
||||
# "UnicodeEncodeError: 'ascii' codec can't encode character"
|
||||
# in Python 2 when the reason contains non-ascii characters.
|
||||
"The candidate selected for download or install is a "
|
||||
"yanked version: {candidate}\n"
|
||||
"Reason for being yanked: {reason}"
|
||||
'The candidate selected for download or install is a '
|
||||
'yanked version: {candidate}\n'
|
||||
'Reason for being yanked: {reason}'
|
||||
).format(candidate=best_candidate, reason=reason)
|
||||
logger.warning(msg)
|
||||
|
||||
|
|
@ -307,7 +308,7 @@ class Resolver(BaseResolver):
|
|||
supported_tags=get_supported(),
|
||||
)
|
||||
if cache_entry is not None:
|
||||
logger.debug("Using cached wheel link: %s", cache_entry.link)
|
||||
logger.debug('Using cached wheel link: %s', cache_entry.link)
|
||||
if req.link is req.original_link and cache_entry.persistent:
|
||||
req.original_link_is_in_wheel_cache = True
|
||||
req.link = cache_entry.link
|
||||
|
|
@ -344,16 +345,16 @@ class Resolver(BaseResolver):
|
|||
|
||||
if req.satisfied_by:
|
||||
should_modify = (
|
||||
self.upgrade_strategy != "to-satisfy-only"
|
||||
or self.force_reinstall
|
||||
or self.ignore_installed
|
||||
or req.link.scheme == "file"
|
||||
self.upgrade_strategy != 'to-satisfy-only' or
|
||||
self.force_reinstall or
|
||||
self.ignore_installed or
|
||||
req.link.scheme == 'file'
|
||||
)
|
||||
if should_modify:
|
||||
self._set_req_to_reinstall(req)
|
||||
else:
|
||||
logger.info(
|
||||
"Requirement already satisfied (use --upgrade to upgrade): %s",
|
||||
'Requirement already satisfied (use --upgrade to upgrade): %s',
|
||||
req,
|
||||
)
|
||||
return dist
|
||||
|
|
@ -362,7 +363,7 @@ class Resolver(BaseResolver):
|
|||
self,
|
||||
requirement_set: RequirementSet,
|
||||
req_to_install: InstallRequirement,
|
||||
) -> List[InstallRequirement]:
|
||||
) -> list[InstallRequirement]:
|
||||
"""Prepare a single requirements file.
|
||||
|
||||
:return: A list of additional InstallRequirements to also install.
|
||||
|
|
@ -385,7 +386,7 @@ class Resolver(BaseResolver):
|
|||
ignore_requires_python=self.ignore_requires_python,
|
||||
)
|
||||
|
||||
more_reqs: List[InstallRequirement] = []
|
||||
more_reqs: list[InstallRequirement] = []
|
||||
|
||||
def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None:
|
||||
# This idiosyncratically converts the Requirement to str and let
|
||||
|
|
@ -415,11 +416,11 @@ class Resolver(BaseResolver):
|
|||
if not self.ignore_dependencies:
|
||||
if req_to_install.extras:
|
||||
logger.debug(
|
||||
"Installing extra requirements: %r",
|
||||
",".join(req_to_install.extras),
|
||||
'Installing extra requirements: %r',
|
||||
','.join(req_to_install.extras),
|
||||
)
|
||||
missing_requested = sorted(
|
||||
set(req_to_install.extras) - set(dist.iter_provided_extras())
|
||||
set(req_to_install.extras) - set(dist.iter_provided_extras()),
|
||||
)
|
||||
for missing in missing_requested:
|
||||
logger.warning(
|
||||
|
|
@ -430,7 +431,7 @@ class Resolver(BaseResolver):
|
|||
)
|
||||
|
||||
available_requested = sorted(
|
||||
set(dist.iter_provided_extras()) & set(req_to_install.extras)
|
||||
set(dist.iter_provided_extras()) & set(req_to_install.extras),
|
||||
)
|
||||
for subreq in dist.iter_dependencies(available_requested):
|
||||
add_req(subreq, extras_requested=available_requested)
|
||||
|
|
@ -438,8 +439,8 @@ class Resolver(BaseResolver):
|
|||
return more_reqs
|
||||
|
||||
def get_installation_order(
|
||||
self, req_set: RequirementSet
|
||||
) -> List[InstallRequirement]:
|
||||
self, req_set: RequirementSet,
|
||||
) -> list[InstallRequirement]:
|
||||
"""Create the installation order.
|
||||
|
||||
The installation order is topological - requirements are installed
|
||||
|
|
@ -450,7 +451,7 @@ class Resolver(BaseResolver):
|
|||
# installs the user specified things in the order given, except when
|
||||
# dependencies must come earlier to achieve topological order.
|
||||
order = []
|
||||
ordered_reqs: Set[InstallRequirement] = set()
|
||||
ordered_reqs: set[InstallRequirement] = set()
|
||||
|
||||
def schedule(req: InstallRequirement) -> None:
|
||||
if req.satisfied_by or req in ordered_reqs:
|
||||
|
|
|
|||
|
|
@ -1,45 +1,53 @@
|
|||
from typing import FrozenSet, Iterable, Optional, Tuple, Union
|
||||
from __future__ import annotations
|
||||
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from pip._vendor.packaging.version import LegacyVersion, Version
|
||||
from typing import FrozenSet
|
||||
from typing import Iterable
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from pip._internal.models.link import Link, links_equivalent
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.models.link import links_equivalent
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.hashes import Hashes
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
from pip._vendor.packaging.version import LegacyVersion
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
CandidateLookup = Tuple[Optional["Candidate"], Optional[InstallRequirement]]
|
||||
CandidateLookup = Tuple[Optional['Candidate'], Optional[InstallRequirement]]
|
||||
CandidateVersion = Union[LegacyVersion, Version]
|
||||
|
||||
|
||||
def format_name(project: str, extras: FrozenSet[str]) -> str:
|
||||
def format_name(project: str, extras: frozenset[str]) -> str:
|
||||
if not extras:
|
||||
return project
|
||||
canonical_extras = sorted(canonicalize_name(e) for e in extras)
|
||||
return "{}[{}]".format(project, ",".join(canonical_extras))
|
||||
return '{}[{}]'.format(project, ','.join(canonical_extras))
|
||||
|
||||
|
||||
class Constraint:
|
||||
def __init__(
|
||||
self, specifier: SpecifierSet, hashes: Hashes, links: FrozenSet[Link]
|
||||
self, specifier: SpecifierSet, hashes: Hashes, links: frozenset[Link],
|
||||
) -> None:
|
||||
self.specifier = specifier
|
||||
self.hashes = hashes
|
||||
self.links = links
|
||||
|
||||
@classmethod
|
||||
def empty(cls) -> "Constraint":
|
||||
def empty(cls) -> Constraint:
|
||||
return Constraint(SpecifierSet(), Hashes(), frozenset())
|
||||
|
||||
@classmethod
|
||||
def from_ireq(cls, ireq: InstallRequirement) -> "Constraint":
|
||||
def from_ireq(cls, ireq: InstallRequirement) -> Constraint:
|
||||
links = frozenset([ireq.link]) if ireq.link else frozenset()
|
||||
return Constraint(ireq.specifier, ireq.hashes(trust_internet=False), links)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self.specifier) or bool(self.hashes) or bool(self.links)
|
||||
|
||||
def __and__(self, other: InstallRequirement) -> "Constraint":
|
||||
def __and__(self, other: InstallRequirement) -> Constraint:
|
||||
if not isinstance(other, InstallRequirement):
|
||||
return NotImplemented
|
||||
specifier = self.specifier & other.specifier
|
||||
|
|
@ -49,7 +57,7 @@ class Constraint:
|
|||
links = links.union([other.link])
|
||||
return Constraint(specifier, hashes, links)
|
||||
|
||||
def is_satisfied_by(self, candidate: "Candidate") -> bool:
|
||||
def is_satisfied_by(self, candidate: Candidate) -> bool:
|
||||
# Reject if there are any mismatched URL constraints on this package.
|
||||
if self.links and not all(_match_link(link, candidate) for link in self.links):
|
||||
return False
|
||||
|
|
@ -68,7 +76,7 @@ class Requirement:
|
|||
in which case ``name`` would contain the ``[...]`` part, while this
|
||||
refers to the name of the project.
|
||||
"""
|
||||
raise NotImplementedError("Subclass should override")
|
||||
raise NotImplementedError('Subclass should override')
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
|
@ -77,19 +85,19 @@ class Requirement:
|
|||
This is different from ``project_name`` if this requirement contains
|
||||
extras, where ``project_name`` would not contain the ``[...]`` part.
|
||||
"""
|
||||
raise NotImplementedError("Subclass should override")
|
||||
raise NotImplementedError('Subclass should override')
|
||||
|
||||
def is_satisfied_by(self, candidate: "Candidate") -> bool:
|
||||
def is_satisfied_by(self, candidate: Candidate) -> bool:
|
||||
return False
|
||||
|
||||
def get_candidate_lookup(self) -> CandidateLookup:
|
||||
raise NotImplementedError("Subclass should override")
|
||||
raise NotImplementedError('Subclass should override')
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
raise NotImplementedError("Subclass should override")
|
||||
raise NotImplementedError('Subclass should override')
|
||||
|
||||
|
||||
def _match_link(link: Link, candidate: "Candidate") -> bool:
|
||||
def _match_link(link: Link, candidate: Candidate) -> bool:
|
||||
if candidate.source_link:
|
||||
return links_equivalent(link, candidate.source_link)
|
||||
return False
|
||||
|
|
@ -104,7 +112,7 @@ class Candidate:
|
|||
in which case ``name`` would contain the ``[...]`` part, while this
|
||||
refers to the name of the project.
|
||||
"""
|
||||
raise NotImplementedError("Override in subclass")
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
|
@ -113,29 +121,29 @@ class Candidate:
|
|||
This is different from ``project_name`` if this candidate contains
|
||||
extras, where ``project_name`` would not contain the ``[...]`` part.
|
||||
"""
|
||||
raise NotImplementedError("Override in subclass")
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
@property
|
||||
def version(self) -> CandidateVersion:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
@property
|
||||
def is_installed(self) -> bool:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
@property
|
||||
def is_editable(self) -> bool:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
@property
|
||||
def source_link(self) -> Optional[Link]:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
def source_link(self) -> Link | None:
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
raise NotImplementedError("Subclass should override")
|
||||
raise NotImplementedError('Subclass should override')
|
||||
|
|
|
|||
|
|
@ -1,26 +1,35 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
from typing import FrozenSet
|
||||
from typing import Iterable
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
from pip._internal.exceptions import (
|
||||
HashError,
|
||||
InstallationSubprocessError,
|
||||
MetadataInconsistent,
|
||||
)
|
||||
from pip._internal.exceptions import HashError
|
||||
from pip._internal.exceptions import InstallationSubprocessError
|
||||
from pip._internal.exceptions import MetadataInconsistent
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.models.link import Link, links_equivalent
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.models.link import links_equivalent
|
||||
from pip._internal.models.wheel import Wheel
|
||||
from pip._internal.req.constructors import (
|
||||
install_req_from_editable,
|
||||
install_req_from_line,
|
||||
)
|
||||
from pip._internal.req.constructors import install_req_from_editable
|
||||
from pip._internal.req.constructors import install_req_from_line
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.utils.misc import normalize_version_info
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
from pip._vendor.packaging.version import Version
|
||||
|
||||
from .base import Candidate, CandidateVersion, Requirement, format_name
|
||||
from .base import Candidate
|
||||
from .base import CandidateVersion
|
||||
from .base import format_name
|
||||
from .base import Requirement
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .factory import Factory
|
||||
|
|
@ -28,16 +37,16 @@ if TYPE_CHECKING:
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
BaseCandidate = Union[
|
||||
"AlreadyInstalledCandidate",
|
||||
"EditableCandidate",
|
||||
"LinkCandidate",
|
||||
'AlreadyInstalledCandidate',
|
||||
'EditableCandidate',
|
||||
'LinkCandidate',
|
||||
]
|
||||
|
||||
# Avoid conflicting with the PyPI package "Python".
|
||||
REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, "<Python from Requires-Python>")
|
||||
REQUIRES_PYTHON_IDENTIFIER = cast(NormalizedName, '<Python from Requires-Python>')
|
||||
|
||||
|
||||
def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
|
||||
def as_base_candidate(candidate: Candidate) -> BaseCandidate | None:
|
||||
"""The runtime version of BaseCandidate."""
|
||||
base_candidate_classes = (
|
||||
AlreadyInstalledCandidate,
|
||||
|
|
@ -50,9 +59,9 @@ def as_base_candidate(candidate: Candidate) -> Optional[BaseCandidate]:
|
|||
|
||||
|
||||
def make_install_req_from_link(
|
||||
link: Link, template: InstallRequirement
|
||||
link: Link, template: InstallRequirement,
|
||||
) -> InstallRequirement:
|
||||
assert not template.editable, "template is editable"
|
||||
assert not template.editable, 'template is editable'
|
||||
if template.req:
|
||||
line = str(template.req)
|
||||
else:
|
||||
|
|
@ -76,9 +85,9 @@ def make_install_req_from_link(
|
|||
|
||||
|
||||
def make_install_req_from_editable(
|
||||
link: Link, template: InstallRequirement
|
||||
link: Link, template: InstallRequirement,
|
||||
) -> InstallRequirement:
|
||||
assert template.editable, "template not editable"
|
||||
assert template.editable, 'template not editable'
|
||||
return install_req_from_editable(
|
||||
link.url,
|
||||
user_supplied=template.user_supplied,
|
||||
|
|
@ -96,14 +105,14 @@ def make_install_req_from_editable(
|
|||
|
||||
|
||||
def _make_install_req_from_dist(
|
||||
dist: BaseDistribution, template: InstallRequirement
|
||||
dist: BaseDistribution, template: InstallRequirement,
|
||||
) -> InstallRequirement:
|
||||
if template.req:
|
||||
line = str(template.req)
|
||||
elif template.link:
|
||||
line = f"{dist.canonical_name} @ {template.link.url}"
|
||||
line = f'{dist.canonical_name} @ {template.link.url}'
|
||||
else:
|
||||
line = f"{dist.canonical_name}=={dist.version}"
|
||||
line = f'{dist.canonical_name}=={dist.version}'
|
||||
ireq = install_req_from_line(
|
||||
line,
|
||||
user_supplied=template.user_supplied,
|
||||
|
|
@ -145,9 +154,9 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
link: Link,
|
||||
source_link: Link,
|
||||
ireq: InstallRequirement,
|
||||
factory: "Factory",
|
||||
name: Optional[NormalizedName] = None,
|
||||
version: Optional[CandidateVersion] = None,
|
||||
factory: Factory,
|
||||
name: NormalizedName | None = None,
|
||||
version: CandidateVersion | None = None,
|
||||
) -> None:
|
||||
self._link = link
|
||||
self._source_link = source_link
|
||||
|
|
@ -158,10 +167,10 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
self.dist = self._prepare()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.name} {self.version}"
|
||||
return f'{self.name} {self.version}'
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({link!r})".format(
|
||||
return '{class_name}({link!r})'.format(
|
||||
class_name=self.__class__.__name__,
|
||||
link=str(self._link),
|
||||
)
|
||||
|
|
@ -175,7 +184,7 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
return False
|
||||
|
||||
@property
|
||||
def source_link(self) -> Optional[Link]:
|
||||
def source_link(self) -> Link | None:
|
||||
return self._source_link
|
||||
|
||||
@property
|
||||
|
|
@ -196,28 +205,28 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
return self._version
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
return "{} {} (from {})".format(
|
||||
return '{} {} (from {})'.format(
|
||||
self.name,
|
||||
self.version,
|
||||
self._link.file_path if self._link.is_file else self._link,
|
||||
)
|
||||
|
||||
def _prepare_distribution(self) -> BaseDistribution:
|
||||
raise NotImplementedError("Override in subclass")
|
||||
raise NotImplementedError('Override in subclass')
|
||||
|
||||
def _check_metadata_consistency(self, dist: BaseDistribution) -> None:
|
||||
"""Check for consistency of project name and version of dist."""
|
||||
if self._name is not None and self._name != dist.canonical_name:
|
||||
raise MetadataInconsistent(
|
||||
self._ireq,
|
||||
"name",
|
||||
'name',
|
||||
self._name,
|
||||
dist.canonical_name,
|
||||
)
|
||||
if self._version is not None and self._version != dist.version:
|
||||
raise MetadataInconsistent(
|
||||
self._ireq,
|
||||
"version",
|
||||
'version',
|
||||
str(self._version),
|
||||
str(dist.version),
|
||||
)
|
||||
|
|
@ -233,19 +242,19 @@ class _InstallRequirementBackedCandidate(Candidate):
|
|||
raise
|
||||
except InstallationSubprocessError as exc:
|
||||
# The output has been presented already, so don't duplicate it.
|
||||
exc.context = "See above for output."
|
||||
exc.context = 'See above for output.'
|
||||
raise
|
||||
|
||||
self._check_metadata_consistency(dist)
|
||||
return dist
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
requires = self.dist.iter_dependencies() if with_requires else ()
|
||||
for r in requires:
|
||||
yield self._factory.make_requirement_from_spec(str(r), self._ireq)
|
||||
yield self._factory.make_requires_python_requirement(self.dist.requires_python)
|
||||
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
return self._ireq
|
||||
|
||||
|
||||
|
|
@ -256,32 +265,32 @@ class LinkCandidate(_InstallRequirementBackedCandidate):
|
|||
self,
|
||||
link: Link,
|
||||
template: InstallRequirement,
|
||||
factory: "Factory",
|
||||
name: Optional[NormalizedName] = None,
|
||||
version: Optional[CandidateVersion] = None,
|
||||
factory: Factory,
|
||||
name: NormalizedName | None = None,
|
||||
version: CandidateVersion | None = None,
|
||||
) -> None:
|
||||
source_link = link
|
||||
cache_entry = factory.get_wheel_cache_entry(link, name)
|
||||
if cache_entry is not None:
|
||||
logger.debug("Using cached wheel link: %s", cache_entry.link)
|
||||
logger.debug('Using cached wheel link: %s', cache_entry.link)
|
||||
link = cache_entry.link
|
||||
ireq = make_install_req_from_link(link, template)
|
||||
assert ireq.link == link
|
||||
if ireq.link.is_wheel and not ireq.link.is_file:
|
||||
wheel = Wheel(ireq.link.filename)
|
||||
wheel_name = canonicalize_name(wheel.name)
|
||||
assert name == wheel_name, f"{name!r} != {wheel_name!r} for wheel"
|
||||
assert name == wheel_name, f'{name!r} != {wheel_name!r} for wheel'
|
||||
# Version may not be present for PEP 508 direct URLs
|
||||
if version is not None:
|
||||
wheel_version = Version(wheel.version)
|
||||
assert version == wheel_version, "{!r} != {!r} for wheel {}".format(
|
||||
version, wheel_version, name
|
||||
assert version == wheel_version, '{!r} != {!r} for wheel {}'.format(
|
||||
version, wheel_version, name,
|
||||
)
|
||||
|
||||
if (
|
||||
cache_entry is not None
|
||||
and cache_entry.persistent
|
||||
and template.link is template.original_link
|
||||
cache_entry is not None and
|
||||
cache_entry.persistent and
|
||||
template.link is template.original_link
|
||||
):
|
||||
ireq.original_link_is_in_wheel_cache = True
|
||||
|
||||
|
|
@ -306,9 +315,9 @@ class EditableCandidate(_InstallRequirementBackedCandidate):
|
|||
self,
|
||||
link: Link,
|
||||
template: InstallRequirement,
|
||||
factory: "Factory",
|
||||
name: Optional[NormalizedName] = None,
|
||||
version: Optional[CandidateVersion] = None,
|
||||
factory: Factory,
|
||||
name: NormalizedName | None = None,
|
||||
version: CandidateVersion | None = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
link=link,
|
||||
|
|
@ -331,7 +340,7 @@ class AlreadyInstalledCandidate(Candidate):
|
|||
self,
|
||||
dist: BaseDistribution,
|
||||
template: InstallRequirement,
|
||||
factory: "Factory",
|
||||
factory: Factory,
|
||||
) -> None:
|
||||
self.dist = dist
|
||||
self._ireq = _make_install_req_from_dist(dist, template)
|
||||
|
|
@ -341,14 +350,14 @@ class AlreadyInstalledCandidate(Candidate):
|
|||
# The returned dist would be exactly the same as self.dist because we
|
||||
# set satisfied_by in _make_install_req_from_dist.
|
||||
# TODO: Supply reason based on force_reinstall and upgrade_strategy.
|
||||
skip_reason = "already satisfied"
|
||||
skip_reason = 'already satisfied'
|
||||
factory.preparer.prepare_installed_requirement(self._ireq, skip_reason)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.dist)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}({distribution!r})".format(
|
||||
return '{class_name}({distribution!r})'.format(
|
||||
class_name=self.__class__.__name__,
|
||||
distribution=self.dist,
|
||||
)
|
||||
|
|
@ -378,15 +387,15 @@ class AlreadyInstalledCandidate(Candidate):
|
|||
return self.dist.editable
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
return f"{self.name} {self.version} (Installed)"
|
||||
return f'{self.name} {self.version} (Installed)'
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
if not with_requires:
|
||||
return
|
||||
for r in self.dist.iter_dependencies():
|
||||
yield self._factory.make_requirement_from_spec(str(r), self._ireq)
|
||||
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
return None
|
||||
|
||||
|
||||
|
|
@ -418,17 +427,17 @@ class ExtrasCandidate(Candidate):
|
|||
def __init__(
|
||||
self,
|
||||
base: BaseCandidate,
|
||||
extras: FrozenSet[str],
|
||||
extras: frozenset[str],
|
||||
) -> None:
|
||||
self.base = base
|
||||
self.extras = extras
|
||||
|
||||
def __str__(self) -> str:
|
||||
name, rest = str(self.base).split(" ", 1)
|
||||
return "{}[{}] {}".format(name, ",".join(self.extras), rest)
|
||||
name, rest = str(self.base).split(' ', 1)
|
||||
return '{}[{}] {}'.format(name, ','.join(self.extras), rest)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{class_name}(base={base!r}, extras={extras!r})".format(
|
||||
return '{class_name}(base={base!r}, extras={extras!r})'.format(
|
||||
class_name=self.__class__.__name__,
|
||||
base=self.base,
|
||||
extras=self.extras,
|
||||
|
|
@ -456,8 +465,8 @@ class ExtrasCandidate(Candidate):
|
|||
return self.base.version
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
return "{} [{}]".format(
|
||||
self.base.format_for_error(), ", ".join(sorted(self.extras))
|
||||
return '{} [{}]'.format(
|
||||
self.base.format_for_error(), ', '.join(sorted(self.extras)),
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
@ -469,10 +478,10 @@ class ExtrasCandidate(Candidate):
|
|||
return self.base.is_editable
|
||||
|
||||
@property
|
||||
def source_link(self) -> Optional[Link]:
|
||||
def source_link(self) -> Link | None:
|
||||
return self.base.source_link
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
factory = self.base._factory
|
||||
|
||||
# Add a dependency on the exact base
|
||||
|
|
@ -495,12 +504,12 @@ class ExtrasCandidate(Candidate):
|
|||
|
||||
for r in self.base.dist.iter_dependencies(valid_extras):
|
||||
requirement = factory.make_requirement_from_spec(
|
||||
str(r), self.base._ireq, valid_extras
|
||||
str(r), self.base._ireq, valid_extras,
|
||||
)
|
||||
if requirement:
|
||||
yield requirement
|
||||
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
# We don't return anything here, because we always
|
||||
# depend on the base candidate, and we'll get the
|
||||
# install requirement from that.
|
||||
|
|
@ -511,19 +520,19 @@ class RequiresPythonCandidate(Candidate):
|
|||
is_installed = False
|
||||
source_link = None
|
||||
|
||||
def __init__(self, py_version_info: Optional[Tuple[int, ...]]) -> None:
|
||||
def __init__(self, py_version_info: tuple[int, ...] | None) -> None:
|
||||
if py_version_info is not None:
|
||||
version_info = normalize_version_info(py_version_info)
|
||||
else:
|
||||
version_info = sys.version_info[:3]
|
||||
self._version = Version(".".join(str(c) for c in version_info))
|
||||
self._version = Version('.'.join(str(c) for c in version_info))
|
||||
|
||||
# We don't need to implement __eq__() and __ne__() since there is always
|
||||
# only one RequiresPythonCandidate in a resolution, i.e. the host Python.
|
||||
# The built-in object.__eq__() and object.__ne__() do exactly what we want.
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Python {self._version}"
|
||||
return f'Python {self._version}'
|
||||
|
||||
@property
|
||||
def project_name(self) -> NormalizedName:
|
||||
|
|
@ -538,10 +547,10 @@ class RequiresPythonCandidate(Candidate):
|
|||
return self._version
|
||||
|
||||
def format_for_error(self) -> str:
|
||||
return f"Python {self.version}"
|
||||
return f'Python {self.version}'
|
||||
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Optional[Requirement]]:
|
||||
def iter_dependencies(self, with_requires: bool) -> Iterable[Requirement | None]:
|
||||
return ()
|
||||
|
||||
def get_install_requirement(self) -> Optional[InstallRequirement]:
|
||||
def get_install_requirement(self) -> InstallRequirement | None:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -1,70 +1,68 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import logging
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
FrozenSet,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Mapping,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
cast,
|
||||
)
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import FrozenSet
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
|
||||
from pip._vendor.packaging.requirements import InvalidRequirement
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
|
||||
from pip._vendor.resolvelib import ResolutionImpossible
|
||||
|
||||
from pip._internal.cache import CacheEntry, WheelCache
|
||||
from pip._internal.exceptions import (
|
||||
DistributionNotFound,
|
||||
InstallationError,
|
||||
InstallationSubprocessError,
|
||||
MetadataInconsistent,
|
||||
UnsupportedPythonVersion,
|
||||
UnsupportedWheel,
|
||||
)
|
||||
from pip._internal.cache import CacheEntry
|
||||
from pip._internal.cache import WheelCache
|
||||
from pip._internal.exceptions import DistributionNotFound
|
||||
from pip._internal.exceptions import InstallationError
|
||||
from pip._internal.exceptions import InstallationSubprocessError
|
||||
from pip._internal.exceptions import MetadataInconsistent
|
||||
from pip._internal.exceptions import UnsupportedPythonVersion
|
||||
from pip._internal.exceptions import UnsupportedWheel
|
||||
from pip._internal.index.package_finder import PackageFinder
|
||||
from pip._internal.metadata import BaseDistribution, get_default_environment
|
||||
from pip._internal.metadata import BaseDistribution
|
||||
from pip._internal.metadata import get_default_environment
|
||||
from pip._internal.models.link import Link
|
||||
from pip._internal.models.wheel import Wheel
|
||||
from pip._internal.operations.prepare import RequirementPreparer
|
||||
from pip._internal.req.constructors import install_req_from_link_and_ireq
|
||||
from pip._internal.req.req_install import (
|
||||
InstallRequirement,
|
||||
check_invalid_constraint_type,
|
||||
)
|
||||
from pip._internal.req.req_install import check_invalid_constraint_type
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
from pip._internal.resolution.base import InstallRequirementProvider
|
||||
from pip._internal.utils.compatibility_tags import get_supported
|
||||
from pip._internal.utils.hashes import Hashes
|
||||
from pip._internal.utils.packaging import get_requirement
|
||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
||||
from pip._vendor.packaging.requirements import InvalidRequirement
|
||||
from pip._vendor.packaging.specifiers import SpecifierSet
|
||||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.packaging.utils import NormalizedName
|
||||
from pip._vendor.resolvelib import ResolutionImpossible
|
||||
|
||||
from .base import Candidate, CandidateVersion, Constraint, Requirement
|
||||
from .candidates import (
|
||||
AlreadyInstalledCandidate,
|
||||
BaseCandidate,
|
||||
EditableCandidate,
|
||||
ExtrasCandidate,
|
||||
LinkCandidate,
|
||||
RequiresPythonCandidate,
|
||||
as_base_candidate,
|
||||
)
|
||||
from .found_candidates import FoundCandidates, IndexCandidateInfo
|
||||
from .requirements import (
|
||||
ExplicitRequirement,
|
||||
RequiresPythonRequirement,
|
||||
SpecifierRequirement,
|
||||
UnsatisfiableRequirement,
|
||||
)
|
||||
from .base import Candidate
|
||||
from .base import CandidateVersion
|
||||
from .base import Constraint
|
||||
from .base import Requirement
|
||||
from .candidates import AlreadyInstalledCandidate
|
||||
from .candidates import as_base_candidate
|
||||
from .candidates import BaseCandidate
|
||||
from .candidates import EditableCandidate
|
||||
from .candidates import ExtrasCandidate
|
||||
from .candidates import LinkCandidate
|
||||
from .candidates import RequiresPythonCandidate
|
||||
from .found_candidates import FoundCandidates
|
||||
from .found_candidates import IndexCandidateInfo
|
||||
from .requirements import ExplicitRequirement
|
||||
from .requirements import RequiresPythonRequirement
|
||||
from .requirements import SpecifierRequirement
|
||||
from .requirements import UnsatisfiableRequirement
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
|
|
@ -76,14 +74,14 @@ if TYPE_CHECKING:
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
C = TypeVar("C")
|
||||
C = TypeVar('C')
|
||||
Cache = Dict[Link, C]
|
||||
|
||||
|
||||
class CollectedRootRequirements(NamedTuple):
|
||||
requirements: List[Requirement]
|
||||
constraints: Dict[str, Constraint]
|
||||
user_requested: Dict[str, int]
|
||||
requirements: list[Requirement]
|
||||
constraints: dict[str, Constraint]
|
||||
user_requested: dict[str, int]
|
||||
|
||||
|
||||
class Factory:
|
||||
|
|
@ -92,13 +90,13 @@ class Factory:
|
|||
finder: PackageFinder,
|
||||
preparer: RequirementPreparer,
|
||||
make_install_req: InstallRequirementProvider,
|
||||
wheel_cache: Optional[WheelCache],
|
||||
wheel_cache: WheelCache | None,
|
||||
use_user_site: bool,
|
||||
force_reinstall: bool,
|
||||
ignore_installed: bool,
|
||||
ignore_requires_python: bool,
|
||||
suppress_build_failures: bool,
|
||||
py_version_info: Optional[Tuple[int, ...]] = None,
|
||||
py_version_info: tuple[int, ...] | None = None,
|
||||
) -> None:
|
||||
self._finder = finder
|
||||
self.preparer = preparer
|
||||
|
|
@ -113,9 +111,9 @@ class Factory:
|
|||
self._build_failures: Cache[InstallationError] = {}
|
||||
self._link_candidate_cache: Cache[LinkCandidate] = {}
|
||||
self._editable_candidate_cache: Cache[EditableCandidate] = {}
|
||||
self._installed_candidate_cache: Dict[str, AlreadyInstalledCandidate] = {}
|
||||
self._extras_candidate_cache: Dict[
|
||||
Tuple[int, FrozenSet[str]], ExtrasCandidate
|
||||
self._installed_candidate_cache: dict[str, AlreadyInstalledCandidate] = {}
|
||||
self._extras_candidate_cache: dict[
|
||||
tuple[int, frozenset[str]], ExtrasCandidate,
|
||||
] = {}
|
||||
|
||||
if not ignore_installed:
|
||||
|
|
@ -137,11 +135,11 @@ class Factory:
|
|||
wheel = Wheel(link.filename)
|
||||
if wheel.supported(self._finder.target_python.get_tags()):
|
||||
return
|
||||
msg = f"{link.filename} is not a supported wheel on this platform."
|
||||
msg = f'{link.filename} is not a supported wheel on this platform.'
|
||||
raise UnsupportedWheel(msg)
|
||||
|
||||
def _make_extras_candidate(
|
||||
self, base: BaseCandidate, extras: FrozenSet[str]
|
||||
self, base: BaseCandidate, extras: frozenset[str],
|
||||
) -> ExtrasCandidate:
|
||||
cache_key = (id(base), extras)
|
||||
try:
|
||||
|
|
@ -154,7 +152,7 @@ class Factory:
|
|||
def _make_candidate_from_dist(
|
||||
self,
|
||||
dist: BaseDistribution,
|
||||
extras: FrozenSet[str],
|
||||
extras: frozenset[str],
|
||||
template: InstallRequirement,
|
||||
) -> Candidate:
|
||||
try:
|
||||
|
|
@ -169,11 +167,11 @@ class Factory:
|
|||
def _make_candidate_from_link(
|
||||
self,
|
||||
link: Link,
|
||||
extras: FrozenSet[str],
|
||||
extras: frozenset[str],
|
||||
template: InstallRequirement,
|
||||
name: Optional[NormalizedName],
|
||||
version: Optional[CandidateVersion],
|
||||
) -> Optional[Candidate]:
|
||||
name: NormalizedName | None,
|
||||
version: CandidateVersion | None,
|
||||
) -> Candidate | None:
|
||||
# TODO: Check already installed candidate, and use it if the link and
|
||||
# editable flag match.
|
||||
|
||||
|
|
@ -194,17 +192,17 @@ class Factory:
|
|||
)
|
||||
except MetadataInconsistent as e:
|
||||
logger.info(
|
||||
"Discarding [blue underline]%s[/]: [yellow]%s[reset]",
|
||||
'Discarding [blue underline]%s[/]: [yellow]%s[reset]',
|
||||
link,
|
||||
e,
|
||||
extra={"markup": True},
|
||||
extra={'markup': True},
|
||||
)
|
||||
self._build_failures[link] = e
|
||||
return None
|
||||
except InstallationSubprocessError as e:
|
||||
if not self._suppress_build_failures:
|
||||
raise
|
||||
logger.warning("Discarding %s due to build failure: %s", link, e)
|
||||
logger.warning('Discarding %s due to build failure: %s', link, e)
|
||||
self._build_failures[link] = e
|
||||
return None
|
||||
|
||||
|
|
@ -221,17 +219,17 @@ class Factory:
|
|||
)
|
||||
except MetadataInconsistent as e:
|
||||
logger.info(
|
||||
"Discarding [blue underline]%s[/]: [yellow]%s[reset]",
|
||||
'Discarding [blue underline]%s[/]: [yellow]%s[reset]',
|
||||
link,
|
||||
e,
|
||||
extra={"markup": True},
|
||||
extra={'markup': True},
|
||||
)
|
||||
self._build_failures[link] = e
|
||||
return None
|
||||
except InstallationSubprocessError as e:
|
||||
if not self._suppress_build_failures:
|
||||
raise
|
||||
logger.warning("Discarding %s due to build failure: %s", link, e)
|
||||
logger.warning('Discarding %s due to build failure: %s', link, e)
|
||||
self._build_failures[link] = e
|
||||
return None
|
||||
base = self._link_candidate_cache[link]
|
||||
|
|
@ -246,7 +244,7 @@ class Factory:
|
|||
specifier: SpecifierSet,
|
||||
hashes: Hashes,
|
||||
prefers_installed: bool,
|
||||
incompatible_ids: Set[int],
|
||||
incompatible_ids: set[int],
|
||||
) -> Iterable[Candidate]:
|
||||
if not ireqs:
|
||||
return ()
|
||||
|
|
@ -256,17 +254,17 @@ class Factory:
|
|||
# all of them.
|
||||
# Hopefully the Project model can correct this mismatch in the future.
|
||||
template = ireqs[0]
|
||||
assert template.req, "Candidates found on index must be PEP 508"
|
||||
assert template.req, 'Candidates found on index must be PEP 508'
|
||||
name = canonicalize_name(template.req.name)
|
||||
|
||||
extras: FrozenSet[str] = frozenset()
|
||||
extras: frozenset[str] = frozenset()
|
||||
for ireq in ireqs:
|
||||
assert ireq.req, "Candidates found on index must be PEP 508"
|
||||
assert ireq.req, 'Candidates found on index must be PEP 508'
|
||||
specifier &= ireq.req.specifier
|
||||
hashes &= ireq.hashes(trust_internet=False)
|
||||
extras |= frozenset(ireq.extras)
|
||||
|
||||
def _get_installed_candidate() -> Optional[Candidate]:
|
||||
def _get_installed_candidate() -> Candidate | None:
|
||||
"""Get the candidate for the currently-installed version."""
|
||||
# If --force-reinstall is set, we want the version from the index
|
||||
# instead, so we "pretend" there is nothing installed.
|
||||
|
|
@ -305,11 +303,11 @@ class Factory:
|
|||
|
||||
def is_pinned(specifier: SpecifierSet) -> bool:
|
||||
for sp in specifier:
|
||||
if sp.operator == "===":
|
||||
if sp.operator == '===':
|
||||
return True
|
||||
if sp.operator != "==":
|
||||
if sp.operator != '==':
|
||||
continue
|
||||
if sp.version.endswith(".*"):
|
||||
if sp.version.endswith('.*'):
|
||||
continue
|
||||
return True
|
||||
return False
|
||||
|
|
@ -340,7 +338,7 @@ class Factory:
|
|||
def _iter_explicit_candidates_from_base(
|
||||
self,
|
||||
base_requirements: Iterable[Requirement],
|
||||
extras: FrozenSet[str],
|
||||
extras: frozenset[str],
|
||||
) -> Iterator[Candidate]:
|
||||
"""Produce explicit candidates from the base given an extra-ed package.
|
||||
|
||||
|
|
@ -356,7 +354,7 @@ class Factory:
|
|||
# We've stripped extras from the identifier, and should always
|
||||
# get a BaseCandidate here, unless there's a bug elsewhere.
|
||||
base_cand = as_base_candidate(lookup_cand)
|
||||
assert base_cand is not None, "no extras here"
|
||||
assert base_cand is not None, 'no extras here'
|
||||
yield self._make_extras_candidate(base_cand, extras)
|
||||
|
||||
def _iter_candidates_from_constraints(
|
||||
|
|
@ -391,8 +389,8 @@ class Factory:
|
|||
prefers_installed: bool,
|
||||
) -> Iterable[Candidate]:
|
||||
# Collect basic lookup information from the requirements.
|
||||
explicit_candidates: Set[Candidate] = set()
|
||||
ireqs: List[InstallRequirement] = []
|
||||
explicit_candidates: set[Candidate] = set()
|
||||
ireqs: list[InstallRequirement] = []
|
||||
for req in requirements[identifier]:
|
||||
cand, ireq = req.get_candidate_lookup()
|
||||
if cand is not None:
|
||||
|
|
@ -447,14 +445,14 @@ class Factory:
|
|||
return (
|
||||
c
|
||||
for c in explicit_candidates
|
||||
if id(c) not in incompat_ids
|
||||
and constraint.is_satisfied_by(c)
|
||||
and all(req.is_satisfied_by(c) for req in requirements[identifier])
|
||||
if id(c) not in incompat_ids and
|
||||
constraint.is_satisfied_by(c) and
|
||||
all(req.is_satisfied_by(c) for req in requirements[identifier])
|
||||
)
|
||||
|
||||
def _make_requirement_from_install_req(
|
||||
self, ireq: InstallRequirement, requested_extras: Iterable[str]
|
||||
) -> Optional[Requirement]:
|
||||
self, ireq: InstallRequirement, requested_extras: Iterable[str],
|
||||
) -> Requirement | None:
|
||||
if not ireq.match_markers(requested_extras):
|
||||
logger.info(
|
||||
"Ignoring %s: markers '%s' don't match your environment",
|
||||
|
|
@ -485,7 +483,7 @@ class Factory:
|
|||
return self.make_requirement_from_candidate(cand)
|
||||
|
||||
def collect_root_requirements(
|
||||
self, root_ireqs: List[InstallRequirement]
|
||||
self, root_ireqs: list[InstallRequirement],
|
||||
) -> CollectedRootRequirements:
|
||||
collected = CollectedRootRequirements([], {}, {})
|
||||
for i, ireq in enumerate(root_ireqs):
|
||||
|
|
@ -496,7 +494,7 @@ class Factory:
|
|||
raise InstallationError(problem)
|
||||
if not ireq.match_markers():
|
||||
continue
|
||||
assert ireq.name, "Constraint must be named"
|
||||
assert ireq.name, 'Constraint must be named'
|
||||
name = canonicalize_name(ireq.name)
|
||||
if name in collected.constraints:
|
||||
collected.constraints[name] &= ireq
|
||||
|
|
@ -515,23 +513,23 @@ class Factory:
|
|||
return collected
|
||||
|
||||
def make_requirement_from_candidate(
|
||||
self, candidate: Candidate
|
||||
self, candidate: Candidate,
|
||||
) -> ExplicitRequirement:
|
||||
return ExplicitRequirement(candidate)
|
||||
|
||||
def make_requirement_from_spec(
|
||||
self,
|
||||
specifier: str,
|
||||
comes_from: Optional[InstallRequirement],
|
||||
comes_from: InstallRequirement | None,
|
||||
requested_extras: Iterable[str] = (),
|
||||
) -> Optional[Requirement]:
|
||||
) -> Requirement | None:
|
||||
ireq = self._make_install_req_from_spec(specifier, comes_from)
|
||||
return self._make_requirement_from_install_req(ireq, requested_extras)
|
||||
|
||||
def make_requires_python_requirement(
|
||||
self,
|
||||
specifier: SpecifierSet,
|
||||
) -> Optional[Requirement]:
|
||||
) -> Requirement | None:
|
||||
if self._ignore_requires_python:
|
||||
return None
|
||||
# Don't bother creating a dependency for an empty Requires-Python.
|
||||
|
|
@ -540,8 +538,8 @@ class Factory:
|
|||
return RequiresPythonRequirement(specifier, self._python_candidate)
|
||||
|
||||
def get_wheel_cache_entry(
|
||||
self, link: Link, name: Optional[str]
|
||||
) -> Optional[CacheEntry]:
|
||||
self, link: Link, name: str | None,
|
||||
) -> CacheEntry | None:
|
||||
"""Look up the link in the wheel cache.
|
||||
|
||||
If ``preparer.require_hashes`` is True, don't use the wheel cache,
|
||||
|
|
@ -558,7 +556,7 @@ class Factory:
|
|||
supported_tags=get_supported(),
|
||||
)
|
||||
|
||||
def get_dist_to_uninstall(self, candidate: Candidate) -> Optional[BaseDistribution]:
|
||||
def get_dist_to_uninstall(self, candidate: Candidate) -> BaseDistribution | None:
|
||||
# TODO: Are there more cases this needs to return True? Editable?
|
||||
dist = self._installed_dists.get(candidate.project_name)
|
||||
if dist is None: # Not installed, no uninstallation required.
|
||||
|
|
@ -580,82 +578,82 @@ class Factory:
|
|||
# in virtual environments, so we error out.
|
||||
if running_under_virtualenv() and dist.in_site_packages:
|
||||
message = (
|
||||
f"Will not install to the user site because it will lack "
|
||||
f"sys.path precedence to {dist.raw_name} in {dist.location}"
|
||||
f'Will not install to the user site because it will lack '
|
||||
f'sys.path precedence to {dist.raw_name} in {dist.location}'
|
||||
)
|
||||
raise InstallationError(message)
|
||||
return None
|
||||
|
||||
def _report_requires_python_error(
|
||||
self, causes: Sequence["ConflictCause"]
|
||||
self, causes: Sequence[ConflictCause],
|
||||
) -> UnsupportedPythonVersion:
|
||||
assert causes, "Requires-Python error reported with no cause"
|
||||
assert causes, 'Requires-Python error reported with no cause'
|
||||
|
||||
version = self._python_candidate.version
|
||||
|
||||
if len(causes) == 1:
|
||||
specifier = str(causes[0].requirement.specifier)
|
||||
message = (
|
||||
f"Package {causes[0].parent.name!r} requires a different "
|
||||
f"Python: {version} not in {specifier!r}"
|
||||
f'Package {causes[0].parent.name!r} requires a different '
|
||||
f'Python: {version} not in {specifier!r}'
|
||||
)
|
||||
return UnsupportedPythonVersion(message)
|
||||
|
||||
message = f"Packages require a different Python. {version} not in:"
|
||||
message = f'Packages require a different Python. {version} not in:'
|
||||
for cause in causes:
|
||||
package = cause.parent.format_for_error()
|
||||
specifier = str(cause.requirement.specifier)
|
||||
message += f"\n{specifier!r} (required by {package})"
|
||||
message += f'\n{specifier!r} (required by {package})'
|
||||
return UnsupportedPythonVersion(message)
|
||||
|
||||
def _report_single_requirement_conflict(
|
||||
self, req: Requirement, parent: Optional[Candidate]
|
||||
self, req: Requirement, parent: Candidate | None,
|
||||
) -> DistributionNotFound:
|
||||
if parent is None:
|
||||
req_disp = str(req)
|
||||
else:
|
||||
req_disp = f"{req} (from {parent.name})"
|
||||
req_disp = f'{req} (from {parent.name})'
|
||||
|
||||
cands = self._finder.find_all_candidates(req.project_name)
|
||||
versions = [str(v) for v in sorted({c.version for c in cands})]
|
||||
|
||||
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_disp,
|
||||
", ".join(versions) or "none",
|
||||
', '.join(versions) or 'none',
|
||||
)
|
||||
if str(req) == "requirements.txt":
|
||||
if str(req) == 'requirements.txt':
|
||||
logger.info(
|
||||
"HINT: You are attempting to install a package literally "
|
||||
'HINT: You are attempting to install a package literally '
|
||||
'named "requirements.txt" (which cannot exist). Consider '
|
||||
"using the '-r' flag to install the packages listed in "
|
||||
"requirements.txt"
|
||||
'requirements.txt',
|
||||
)
|
||||
|
||||
return DistributionNotFound(f"No matching distribution found for {req}")
|
||||
return DistributionNotFound(f'No matching distribution found for {req}')
|
||||
|
||||
def get_installation_error(
|
||||
self,
|
||||
e: "ResolutionImpossible[Requirement, Candidate]",
|
||||
constraints: Dict[str, Constraint],
|
||||
e: ResolutionImpossible[Requirement, Candidate],
|
||||
constraints: dict[str, Constraint],
|
||||
) -> InstallationError:
|
||||
|
||||
assert e.causes, "Installation error reported with no cause"
|
||||
assert e.causes, 'Installation error reported with no cause'
|
||||
|
||||
# If one of the things we can't solve is "we need Python X.Y",
|
||||
# that is what we report.
|
||||
requires_python_causes = [
|
||||
cause
|
||||
for cause in e.causes
|
||||
if isinstance(cause.requirement, RequiresPythonRequirement)
|
||||
and not cause.requirement.is_satisfied_by(self._python_candidate)
|
||||
if isinstance(cause.requirement, RequiresPythonRequirement) and
|
||||
not cause.requirement.is_satisfied_by(self._python_candidate)
|
||||
]
|
||||
if requires_python_causes:
|
||||
# The comprehension above makes sure all Requirement instances are
|
||||
# RequiresPythonRequirement, so let's cast for convenience.
|
||||
return self._report_requires_python_error(
|
||||
cast("Sequence[ConflictCause]", requires_python_causes),
|
||||
cast('Sequence[ConflictCause]', requires_python_causes),
|
||||
)
|
||||
|
||||
# Otherwise, we have a set of causes which can't all be satisfied
|
||||
|
|
@ -672,16 +670,16 @@ class Factory:
|
|||
# satisfied at once.
|
||||
|
||||
# A couple of formatting helpers
|
||||
def text_join(parts: List[str]) -> str:
|
||||
def text_join(parts: list[str]) -> str:
|
||||
if len(parts) == 1:
|
||||
return parts[0]
|
||||
|
||||
return ", ".join(parts[:-1]) + " and " + parts[-1]
|
||||
return ', '.join(parts[:-1]) + ' and ' + parts[-1]
|
||||
|
||||
def describe_trigger(parent: Candidate) -> str:
|
||||
ireq = parent.get_install_requirement()
|
||||
if not ireq or not ireq.comes_from:
|
||||
return f"{parent.name}=={parent.version}"
|
||||
return f'{parent.name}=={parent.version}'
|
||||
if isinstance(ireq.comes_from, InstallRequirement):
|
||||
return str(ireq.comes_from.name)
|
||||
return str(ireq.comes_from)
|
||||
|
|
@ -698,42 +696,42 @@ class Factory:
|
|||
if triggers:
|
||||
info = text_join(sorted(triggers))
|
||||
else:
|
||||
info = "the requested packages"
|
||||
info = 'the requested packages'
|
||||
|
||||
msg = (
|
||||
"Cannot install {} because these package versions "
|
||||
"have conflicting dependencies.".format(info)
|
||||
'Cannot install {} because these package versions '
|
||||
'have conflicting dependencies.'.format(info)
|
||||
)
|
||||
logger.critical(msg)
|
||||
msg = "\nThe conflict is caused by:"
|
||||
msg = '\nThe conflict is caused by:'
|
||||
|
||||
relevant_constraints = set()
|
||||
for req, parent in e.causes:
|
||||
if req.name in constraints:
|
||||
relevant_constraints.add(req.name)
|
||||
msg = msg + "\n "
|
||||
msg = msg + '\n '
|
||||
if parent:
|
||||
msg = msg + f"{parent.name} {parent.version} depends on "
|
||||
msg = msg + f'{parent.name} {parent.version} depends on '
|
||||
else:
|
||||
msg = msg + "The user requested "
|
||||
msg = msg + 'The user requested '
|
||||
msg = msg + req.format_for_error()
|
||||
for key in relevant_constraints:
|
||||
spec = constraints[key].specifier
|
||||
msg += f"\n The user requested (constraint) {key}{spec}"
|
||||
msg += f'\n The user requested (constraint) {key}{spec}'
|
||||
|
||||
msg = (
|
||||
msg
|
||||
+ "\n\n"
|
||||
+ "To fix this you could try to:\n"
|
||||
+ "1. loosen the range of package versions you've specified\n"
|
||||
+ "2. remove package versions to allow pip attempt to solve "
|
||||
+ "the dependency conflict\n"
|
||||
msg +
|
||||
'\n\n' +
|
||||
'To fix this you could try to:\n' +
|
||||
"1. loosen the range of package versions you've specified\n" +
|
||||
'2. remove package versions to allow pip attempt to solve ' +
|
||||
'the dependency conflict\n'
|
||||
)
|
||||
|
||||
logger.info(msg)
|
||||
|
||||
return DistributionNotFound(
|
||||
"ResolutionImpossible: for help visit "
|
||||
"https://pip.pypa.io/en/latest/topics/dependency-resolution/"
|
||||
"#dealing-with-dependency-conflicts"
|
||||
'ResolutionImpossible: for help visit '
|
||||
'https://pip.pypa.io/en/latest/topics/dependency-resolution/'
|
||||
'#dealing-with-dependency-conflicts',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,17 @@ everything here lazy all the way down, so we only touch candidates that we
|
|||
absolutely need, and not "download the world" when we only need one version of
|
||||
something.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from collections.abc import Sequence
|
||||
from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Set, Tuple
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Iterator
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pip._vendor.packaging.version import _BaseVersion
|
||||
|
||||
|
|
@ -40,7 +47,7 @@ def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]:
|
|||
This iterator is used when the package is not already installed. Candidates
|
||||
from index come later in their normal ordering.
|
||||
"""
|
||||
versions_found: Set[_BaseVersion] = set()
|
||||
versions_found: set[_BaseVersion] = set()
|
||||
for version, func in infos:
|
||||
if version in versions_found:
|
||||
continue
|
||||
|
|
@ -52,7 +59,7 @@ def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]:
|
|||
|
||||
|
||||
def _iter_built_with_prepended(
|
||||
installed: Candidate, infos: Iterator[IndexCandidateInfo]
|
||||
installed: Candidate, infos: Iterator[IndexCandidateInfo],
|
||||
) -> Iterator[Candidate]:
|
||||
"""Iterator for ``FoundCandidates``.
|
||||
|
||||
|
|
@ -62,7 +69,7 @@ def _iter_built_with_prepended(
|
|||
normal ordering, except skipped when the version is already installed.
|
||||
"""
|
||||
yield installed
|
||||
versions_found: Set[_BaseVersion] = {installed.version}
|
||||
versions_found: set[_BaseVersion] = {installed.version}
|
||||
for version, func in infos:
|
||||
if version in versions_found:
|
||||
continue
|
||||
|
|
@ -74,7 +81,7 @@ def _iter_built_with_prepended(
|
|||
|
||||
|
||||
def _iter_built_with_inserted(
|
||||
installed: Candidate, infos: Iterator[IndexCandidateInfo]
|
||||
installed: Candidate, infos: Iterator[IndexCandidateInfo],
|
||||
) -> Iterator[Candidate]:
|
||||
"""Iterator for ``FoundCandidates``.
|
||||
|
||||
|
|
@ -86,7 +93,7 @@ def _iter_built_with_inserted(
|
|||
the installed candidate exactly once before we start yielding older or
|
||||
equivalent candidates, or after all other candidates if they are all newer.
|
||||
"""
|
||||
versions_found: Set[_BaseVersion] = set()
|
||||
versions_found: set[_BaseVersion] = set()
|
||||
for version, func in infos:
|
||||
if version in versions_found:
|
||||
continue
|
||||
|
|
@ -117,9 +124,9 @@ class FoundCandidates(SequenceCandidate):
|
|||
def __init__(
|
||||
self,
|
||||
get_infos: Callable[[], Iterator[IndexCandidateInfo]],
|
||||
installed: Optional[Candidate],
|
||||
installed: Candidate | None,
|
||||
prefers_installed: bool,
|
||||
incompatible_ids: Set[int],
|
||||
incompatible_ids: set[int],
|
||||
):
|
||||
self._get_infos = get_infos
|
||||
self._installed = installed
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import math
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Mapping,
|
||||
Sequence,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import Mapping
|
||||
from typing import Sequence
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from pip._vendor.resolvelib.providers import AbstractProvider
|
||||
|
||||
from .base import Candidate, Constraint, Requirement
|
||||
from .base import Candidate
|
||||
from .base import Constraint
|
||||
from .base import Requirement
|
||||
from .candidates import REQUIRES_PYTHON_IDENTIFIER
|
||||
from .factory import Factory
|
||||
|
||||
|
|
@ -46,15 +48,15 @@ else:
|
|||
# services to those objects (access to pip's finder and preparer).
|
||||
|
||||
|
||||
D = TypeVar("D")
|
||||
V = TypeVar("V")
|
||||
D = TypeVar('D')
|
||||
V = TypeVar('V')
|
||||
|
||||
|
||||
def _get_with_identifier(
|
||||
mapping: Mapping[str, V],
|
||||
identifier: str,
|
||||
default: D,
|
||||
) -> Union[D, V]:
|
||||
) -> D | V:
|
||||
"""Get item from a package name lookup mapping with a resolver identifier.
|
||||
|
||||
This extra logic is needed when the target mapping is keyed by package
|
||||
|
|
@ -69,7 +71,7 @@ def _get_with_identifier(
|
|||
# some regular expression. But since pip's resolver only spits out three
|
||||
# kinds of identifiers: normalized PEP 503 names, normalized names plus
|
||||
# extras, and Requires-Python, we can cheat a bit here.
|
||||
name, open_bracket, _ = identifier.partition("[")
|
||||
name, open_bracket, _ = identifier.partition('[')
|
||||
if open_bracket and name in mapping:
|
||||
return mapping[name]
|
||||
return default
|
||||
|
|
@ -89,19 +91,19 @@ class PipProvider(_ProviderBase):
|
|||
def __init__(
|
||||
self,
|
||||
factory: Factory,
|
||||
constraints: Dict[str, Constraint],
|
||||
constraints: dict[str, Constraint],
|
||||
ignore_dependencies: bool,
|
||||
upgrade_strategy: str,
|
||||
user_requested: Dict[str, int],
|
||||
user_requested: dict[str, int],
|
||||
) -> None:
|
||||
self._factory = factory
|
||||
self._constraints = constraints
|
||||
self._ignore_dependencies = ignore_dependencies
|
||||
self._upgrade_strategy = upgrade_strategy
|
||||
self._user_requested = user_requested
|
||||
self._known_depths: Dict[str, float] = collections.defaultdict(lambda: math.inf)
|
||||
self._known_depths: dict[str, float] = collections.defaultdict(lambda: math.inf)
|
||||
|
||||
def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
|
||||
def identify(self, requirement_or_candidate: Requirement | Candidate) -> str:
|
||||
return requirement_or_candidate.name
|
||||
|
||||
def get_preference( # type: ignore
|
||||
|
|
@ -109,9 +111,9 @@ class PipProvider(_ProviderBase):
|
|||
identifier: str,
|
||||
resolutions: Mapping[str, Candidate],
|
||||
candidates: Mapping[str, Iterator[Candidate]],
|
||||
information: Mapping[str, Iterable["PreferenceInformation"]],
|
||||
backtrack_causes: Sequence["PreferenceInformation"],
|
||||
) -> "Preference":
|
||||
information: Mapping[str, Iterable[PreferenceInformation]],
|
||||
backtrack_causes: Sequence[PreferenceInformation],
|
||||
) -> Preference:
|
||||
"""Produce a sort key for given requirement based on preference.
|
||||
|
||||
The lower the return value is, the more preferred this group of
|
||||
|
|
@ -139,11 +141,11 @@ class PipProvider(_ProviderBase):
|
|||
]
|
||||
|
||||
direct = candidate is not None
|
||||
pinned = any(op[:2] == "==" for op in operators)
|
||||
pinned = any(op[:2] == '==' for op in operators)
|
||||
unfree = bool(operators)
|
||||
|
||||
try:
|
||||
requested_order: Union[int, float] = self._user_requested[identifier]
|
||||
requested_order: int | float = self._user_requested[identifier]
|
||||
except KeyError:
|
||||
requested_order = math.inf
|
||||
parent_depths = (
|
||||
|
|
@ -169,7 +171,7 @@ class PipProvider(_ProviderBase):
|
|||
# delaying Setuptools helps reduce branches the resolver has to check.
|
||||
# This serves as a temporary fix for issues like "apache-airflow[all]"
|
||||
# while we work on "proper" branch pruning techniques.
|
||||
delay_this = identifier == "setuptools"
|
||||
delay_this = identifier == 'setuptools'
|
||||
|
||||
# Prefer the causes of backtracking on the assumption that the problem
|
||||
# resolving the dependency tree is related to the failures that caused
|
||||
|
|
@ -205,9 +207,9 @@ class PipProvider(_ProviderBase):
|
|||
an upgrade strategy of "to-satisfy-only" means that `--upgrade`
|
||||
was not specified).
|
||||
"""
|
||||
if self._upgrade_strategy == "eager":
|
||||
if self._upgrade_strategy == 'eager':
|
||||
return True
|
||||
elif self._upgrade_strategy == "only-if-needed":
|
||||
elif self._upgrade_strategy == 'only-if-needed':
|
||||
user_order = _get_with_identifier(
|
||||
self._user_requested,
|
||||
identifier,
|
||||
|
|
@ -238,7 +240,7 @@ class PipProvider(_ProviderBase):
|
|||
|
||||
@staticmethod
|
||||
def is_backtrack_cause(
|
||||
identifier: str, backtrack_causes: Sequence["PreferenceInformation"]
|
||||
identifier: str, backtrack_causes: Sequence[PreferenceInformation],
|
||||
) -> bool:
|
||||
for backtrack_cause in backtrack_causes:
|
||||
if identifier == backtrack_cause.requirement.name:
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue