Eliminado venv y www del repositorio, agrege un requirements igual

This commit is contained in:
2020-11-22 21:14:46 -03:00
parent 18cf2d335a
commit 199a1e2a61
820 changed files with 15495 additions and 22017 deletions

View File

@@ -1,77 +1,17 @@
#!/usr/bin/env python
from __future__ import absolute_import
import pip._internal.utils.inject_securetransport # noqa
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
import locale
import logging
import os
import warnings
import sys
# 2016-06-17 barry@debian.org: urllib3 1.14 added optional support for socks,
# but if invoked (i.e. imported), it will issue a warning to stderr if socks
# isn't available. requests unconditionally imports urllib3's socks contrib
# module, triggering this warning. The warning breaks DEP-8 tests (because of
# the stderr output) and is just plain annoying in normal usage. I don't want
# to add socks as yet another dependency for pip, nor do I want to allow-stderr
# in the DEP-8 tests, so just suppress the warning. pdb tells me this has to
# be done before the import of pip.vcs.
from pip._vendor.urllib3.exceptions import DependencyWarning
warnings.filterwarnings("ignore", category=DependencyWarning) # noqa
# We want to inject the use of SecureTransport as early as possible so that any
# references or sessions or what have you are ensured to have it, however we
# only want to do this in the case that we're running on macOS and the linked
# OpenSSL is too old to handle TLSv1.2
try:
import ssl
except ImportError:
pass
else:
# Checks for OpenSSL 1.0.1 on MacOS
if sys.platform == "darwin" and ssl.OPENSSL_VERSION_NUMBER < 0x1000100f:
try:
from pip._vendor.urllib3.contrib import securetransport
except (ImportError, OSError):
pass
else:
securetransport.inject_into_urllib3()
from pip._internal.cli.autocompletion import autocomplete
from pip._internal.cli.main_parser import parse_command
from pip._internal.commands import commands_dict
from pip._internal.exceptions import PipError
from pip._internal.utils import deprecation
from pip._vendor.urllib3.exceptions import InsecureRequestWarning
logger = logging.getLogger(__name__)
# Hide the InsecureRequestWarning from urllib3
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
if MYPY_CHECK_RUNNING:
from typing import Optional, List
def main(args=None):
if args is None:
args = sys.argv[1:]
# type: (Optional[List[str]]) -> int
"""This is preserved for old console scripts that may still be referencing
it.
# Configure our deprecation warnings to be sent through loggers
deprecation.install_warning_logger()
For additional details, see https://github.com/pypa/pip/issues/7498.
"""
from pip._internal.utils.entrypoints import _wrapper
autocomplete()
try:
cmd_name, cmd_args = parse_command(args)
except PipError as exc:
sys.stderr.write("ERROR: %s" % 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, '')
except locale.Error as e:
# setlocale can apparently crash if locale are uninitialized
logger.debug("Ignoring error %s when setting locale", e)
command = commands_dict[cmd_name](isolated=("--isolated" in cmd_args))
return command.main(cmd_args)
return _wrapper(args)

View File

@@ -12,14 +12,15 @@ from sysconfig import get_paths
from pip._vendor.pkg_resources import Requirement, VersionConflict, WorkingSet
from pip import __file__ as pip_location
from pip._internal.utils.misc import call_subprocess
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.cli.spinners import open_spinner
from pip._internal.utils.subprocess import call_subprocess
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.ui import open_spinner
if MYPY_CHECK_RUNNING:
from typing import Tuple, Set, Iterable, Optional, List
from pip._internal.index import PackageFinder
from types import TracebackType
from typing import Tuple, Set, Iterable, Optional, List, Type
from pip._internal.index.package_finder import PackageFinder
logger = logging.getLogger(__name__)
@@ -50,11 +51,12 @@ class BuildEnvironment(object):
def __init__(self):
# type: () -> None
self._temp_dir = TempDirectory(kind="build-env")
self._temp_dir.create()
temp_dir = TempDirectory(
kind=tempdir_kinds.BUILD_ENV, globally_managed=True
)
self._prefixes = OrderedDict((
(name, _Prefix(os.path.join(self._temp_dir.path, name)))
(name, _Prefix(os.path.join(temp_dir.path, name)))
for name in ('normal', 'overlay')
))
@@ -73,7 +75,7 @@ class BuildEnvironment(object):
get_python_lib(plat_specific=True),
)
}
self._site_dir = os.path.join(self._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') as fp:
@@ -105,6 +107,7 @@ class BuildEnvironment(object):
).format(system_sites=system_sites, lib_dirs=self._lib_dirs))
def __enter__(self):
# type: () -> None
self._save_env = {
name: os.environ.get(name, None)
for name in ('PATH', 'PYTHONNOUSERSITE', 'PYTHONPATH')
@@ -123,17 +126,19 @@ class BuildEnvironment(object):
'PYTHONPATH': os.pathsep.join(pythonpath),
})
def __exit__(self, exc_type, exc_val, exc_tb):
def __exit__(
self,
exc_type, # type: Optional[Type[BaseException]]
exc_val, # type: Optional[BaseException]
exc_tb # type: Optional[TracebackType]
):
# type: (...) -> None
for varname, old_value in self._save_env.items():
if old_value is None:
os.environ.pop(varname, None)
else:
os.environ[varname] = old_value
def cleanup(self):
# type: () -> None
self._temp_dir.cleanup()
def check_requirements(self, reqs):
# type: (Iterable[str]) -> Tuple[Set[Tuple[str, str]], Set[str]]
"""Return 2 sets:
@@ -158,7 +163,7 @@ class BuildEnvironment(object):
finder, # type: PackageFinder
requirements, # type: Iterable[str]
prefix_as_string, # type: str
message # type: Optional[str]
message # type: str
):
# type: (...) -> None
prefix = self._prefixes[prefix_as_string]
@@ -192,6 +197,8 @@ class BuildEnvironment(object):
args.extend(['--trusted-host', host])
if finder.allow_all_prereleases:
args.append('--pre')
if finder.prefer_binary:
args.append('--prefer-binary')
args.append('--')
args.extend(requirements)
with open_spinner(message) as spinner:
@@ -203,16 +210,32 @@ class NoOpBuildEnvironment(BuildEnvironment):
"""
def __init__(self):
# type: () -> None
pass
def __enter__(self):
# type: () -> None
pass
def __exit__(self, exc_type, exc_val, exc_tb):
def __exit__(
self,
exc_type, # type: Optional[Type[BaseException]]
exc_val, # type: Optional[BaseException]
exc_tb # type: Optional[TracebackType]
):
# type: (...) -> None
pass
def cleanup(self):
# type: () -> None
pass
def install_requirements(self, finder, requirements, prefix, message):
def install_requirements(
self,
finder, # type: PackageFinder
requirements, # type: Iterable[str]
prefix_as_string, # type: str
message # type: str
):
# type: (...) -> None
raise NotImplementedError()

View File

@@ -1,27 +1,38 @@
"""Cache Management
"""
import errno
import hashlib
import json
import logging
import os
from pip._vendor.packaging.tags import interpreter_name, interpreter_version
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.exceptions import InvalidWheelFilename
from pip._internal.models.link import Link
from pip._internal.utils.compat import expanduser
from pip._internal.utils.misc import path_to_url
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.models.wheel import Wheel
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.wheel import InvalidWheelFilename, Wheel
from pip._internal.utils.urls import path_to_url
if MYPY_CHECK_RUNNING:
from typing import Optional, Set, List, Any
from pip._internal.index import FormatControl
from typing import Optional, Set, List, Any, Dict
from pip._vendor.packaging.tags import Tag
from pip._internal.models.format_control import FormatControl
logger = logging.getLogger(__name__)
def _hash_dict(d):
# type: (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()
class Cache(object):
"""An abstract class - provides cache directories for data from links
@@ -36,16 +47,19 @@ class Cache(object):
def __init__(self, cache_dir, format_control, allowed_formats):
# type: (str, FormatControl, Set[str]) -> None
super(Cache, self).__init__()
self.cache_dir = expanduser(cache_dir) if cache_dir else None
assert not cache_dir or os.path.isabs(cache_dir)
self.cache_dir = cache_dir or None
self.format_control = format_control
self.allowed_formats = allowed_formats
_valid_formats = {"source", "binary"}
assert self.allowed_formats.union(_valid_formats) == _valid_formats
def _get_cache_path_parts(self, link):
def _get_cache_path_parts_legacy(self, link):
# type: (Link) -> List[str]
"""Get parts of part that must be os.path.joined with cache_dir
Legacy cache key (pip < 20) for compatibility with older caches.
"""
# We want to generate an url to use as our cache key, we don't want to
@@ -69,30 +83,72 @@ class Cache(object):
return parts
def _get_candidates(self, link, package_name):
# type: (Link, Optional[str]) -> List[Any]
def _get_cache_path_parts(self, link):
# type: (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}
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
# 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()
# 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
# thus less secure). However the differences don't make a lot of
# difference for our use case here.
hashed = _hash_dict(key_parts)
# We want to nest the directories some to prevent having a ton of top
# level directories where we might run out of sub directories on some
# FS.
parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
return parts
def _get_candidates(self, link, canonical_package_name):
# type: (Link, str) -> List[Any]
can_not_cache = (
not self.cache_dir or
not package_name or
not canonical_package_name or
not link
)
if can_not_cache:
return []
canonical_name = canonicalize_name(package_name)
formats = self.format_control.get_allowed_formats(
canonical_name
canonical_package_name
)
if not self.allowed_formats.intersection(formats):
return []
root = self.get_path_for_link(link)
try:
return os.listdir(root)
except OSError as err:
if err.errno in {errno.ENOENT, errno.ENOTDIR}:
return []
raise
candidates = []
path = self.get_path_for_link(link)
if os.path.isdir(path):
for candidate in os.listdir(path):
candidates.append((candidate, path))
# TODO remove legacy path lookup in pip>=21
legacy_path = self.get_path_for_link_legacy(link)
if os.path.isdir(legacy_path):
for candidate in os.listdir(legacy_path):
candidates.append((candidate, legacy_path))
return candidates
def get_path_for_link_legacy(self, link):
# type: (Link) -> str
raise NotImplementedError()
def get_path_for_link(self, link):
# type: (Link) -> str
@@ -100,24 +156,18 @@ class Cache(object):
"""
raise NotImplementedError()
def get(self, link, package_name):
# type: (Link, Optional[str]) -> Link
def get(
self,
link, # type: Link
package_name, # type: Optional[str]
supported_tags, # type: List[Tag]
):
# type: (...) -> Link
"""Returns a link to a cached item if it exists, otherwise returns the
passed link.
"""
raise NotImplementedError()
def _link_for_candidate(self, link, candidate):
# type: (Link, str) -> Link
root = self.get_path_for_link(link)
path = os.path.join(root, candidate)
return Link(path_to_url(path))
def cleanup(self):
# type: () -> None
pass
class SimpleWheelCache(Cache):
"""A cache of wheels for future installs.
@@ -129,6 +179,12 @@ class SimpleWheelCache(Cache):
cache_dir, format_control, {"binary"}
)
def get_path_for_link_legacy(self, link):
# type: (Link) -> str
parts = self._get_cache_path_parts_legacy(link)
assert self.cache_dir
return os.path.join(self.cache_dir, "wheels", *parts)
def get_path_for_link(self, link):
# type: (Link) -> str
"""Return a directory to store cached wheels for link
@@ -146,28 +202,53 @@ class SimpleWheelCache(Cache):
:param link: The link of the sdist for which this will cache wheels.
"""
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)
def get(self, link, package_name):
# type: (Link, Optional[str]) -> Link
def get(
self,
link, # type: Link
package_name, # type: Optional[str]
supported_tags, # type: List[Tag]
):
# type: (...) -> Link
candidates = []
for wheel_name in self._get_candidates(link, package_name):
if not package_name:
return link
canonical_package_name = canonicalize_name(package_name)
for wheel_name, wheel_dir in self._get_candidates(
link, canonical_package_name
):
try:
wheel = Wheel(wheel_name)
except InvalidWheelFilename:
continue
if not wheel.supported():
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.",
wheel_name, link, package_name,
)
continue
if not wheel.supported(supported_tags):
# Built for a different python/arch/etc
continue
candidates.append((wheel.support_index_min(), wheel_name))
candidates.append(
(
wheel.support_index_min(supported_tags),
wheel_name,
wheel_dir,
)
)
if not candidates:
return link
return self._link_for_candidate(link, min(candidates)[1])
_, wheel_name, wheel_dir = min(candidates)
return Link(path_to_url(os.path.join(wheel_dir, wheel_name)))
class EphemWheelCache(SimpleWheelCache):
@@ -176,16 +257,24 @@ class EphemWheelCache(SimpleWheelCache):
def __init__(self, format_control):
# type: (FormatControl) -> None
self._temp_dir = TempDirectory(kind="ephem-wheel-cache")
self._temp_dir.create()
self._temp_dir = TempDirectory(
kind=tempdir_kinds.EPHEM_WHEEL_CACHE,
globally_managed=True,
)
super(EphemWheelCache, self).__init__(
self._temp_dir.path, format_control
)
def cleanup(self):
# type: () -> None
self._temp_dir.cleanup()
class CacheEntry(object):
def __init__(
self,
link, # type: Link
persistent, # type: bool
):
self.link = link
self.persistent = persistent
class WheelCache(Cache):
@@ -203,6 +292,10 @@ class WheelCache(Cache):
self._wheel_cache = SimpleWheelCache(cache_dir, format_control)
self._ephem_cache = EphemWheelCache(format_control)
def get_path_for_link_legacy(self, link):
# type: (Link) -> str
return self._wheel_cache.get_path_for_link_legacy(link)
def get_path_for_link(self, link):
# type: (Link) -> str
return self._wheel_cache.get_path_for_link(link)
@@ -211,14 +304,43 @@ class WheelCache(Cache):
# type: (Link) -> str
return self._ephem_cache.get_path_for_link(link)
def get(self, link, package_name):
# type: (Link, Optional[str]) -> Link
retval = self._wheel_cache.get(link, package_name)
if retval is link:
retval = self._ephem_cache.get(link, package_name)
return retval
def get(
self,
link, # type: Link
package_name, # type: Optional[str]
supported_tags, # type: List[Tag]
):
# type: (...) -> Link
cache_entry = self.get_cache_entry(link, package_name, supported_tags)
if cache_entry is None:
return link
return cache_entry.link
def cleanup(self):
# type: () -> None
self._wheel_cache.cleanup()
self._ephem_cache.cleanup()
def get_cache_entry(
self,
link, # type: Link
package_name, # type: Optional[str]
supported_tags, # type: List[Tag]
):
# type: (...) -> Optional[CacheEntry]
"""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.
"""
retval = self._wheel_cache.get(
link=link,
package_name=package_name,
supported_tags=supported_tags,
)
if retval is not link:
return CacheEntry(retval, persistent=True)
retval = self._ephem_cache.get(
link=link,
package_name=package_name,
supported_tags=supported_tags,
)
if retval is not link:
return CacheEntry(retval, persistent=False)
return None

View File

@@ -4,13 +4,19 @@
import optparse
import os
import sys
from itertools import chain
from pip._internal.cli.main_parser import create_main_parser
from pip._internal.commands import commands_dict, get_summaries
from pip._internal.commands import commands_dict, create_command
from pip._internal.utils.misc import get_installed_distributions
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Any, Iterable, List, Optional
def autocomplete():
# type: () -> None
"""Entry Point for completion of main and subcommand options.
"""
# Don't complete if user hasn't sourced bash_completion file.
@@ -23,17 +29,18 @@ def autocomplete():
except IndexError:
current = ''
subcommands = [cmd for cmd, summary in get_summaries()]
options = []
# subcommand
try:
subcommand_name = [w for w in cwords if w in subcommands][0]
except IndexError:
subcommand_name = None
parser = create_main_parser()
subcommands = list(commands_dict)
options = []
# subcommand
subcommand_name = None # type: Optional[str]
for word in cwords:
if word in subcommands:
subcommand_name = word
break
# subcommand options
if subcommand_name:
if subcommand_name is not None:
# special case: 'help' subcommand has no options
if subcommand_name == 'help':
sys.exit(1)
@@ -54,7 +61,7 @@ def autocomplete():
print(dist)
sys.exit(1)
subcommand = commands_dict[subcommand_name]()
subcommand = create_command(subcommand_name)
for opt in subcommand.parser.option_list_all:
if opt.help != optparse.SUPPRESS_HELP:
@@ -73,8 +80,8 @@ def autocomplete():
# get completion files and directories if ``completion_type`` is
# ``<file>``, ``<dir>`` or ``<path>``
if completion_type:
options = auto_complete_paths(current, completion_type)
options = ((opt, 0) for opt in options)
paths = auto_complete_paths(current, completion_type)
options = [(path, 0) for path in paths]
for option in options:
opt_label = option[0]
# append '=' to options which require args
@@ -86,22 +93,25 @@ def autocomplete():
opts = [i.option_list for i in parser.option_groups]
opts.append(parser.option_list)
opts = (o for it in opts for o in it)
flattened_opts = chain.from_iterable(opts)
if current.startswith('-'):
for opt in opts:
for opt in flattened_opts:
if opt.help != optparse.SUPPRESS_HELP:
subcommands += opt._long_opts + opt._short_opts
else:
# get completion type given cwords and all available options
completion_type = get_path_completion_type(cwords, cword, opts)
completion_type = get_path_completion_type(cwords, cword,
flattened_opts)
if completion_type:
subcommands = auto_complete_paths(current, completion_type)
subcommands = list(auto_complete_paths(current,
completion_type))
print(' '.join([x for x in subcommands if x.startswith(current)]))
sys.exit(1)
def get_path_completion_type(cwords, cword, opts):
# type: (List[str], int, Iterable[Any]) -> Optional[str]
"""Get the type of path completion (``file``, ``dir``, ``path`` or None)
:param cwords: same as the environmental variable ``COMP_WORDS``
@@ -110,7 +120,7 @@ def get_path_completion_type(cwords, cword, opts):
:return: path completion type (``file``, ``dir``, ``path`` or None)
"""
if cword < 2 or not cwords[cword - 2].startswith('-'):
return
return None
for opt in opts:
if opt.help == optparse.SUPPRESS_HELP:
continue
@@ -120,9 +130,11 @@ def get_path_completion_type(cwords, cword, opts):
x in ('path', 'file', 'dir')
for x in opt.metavar.split('/')):
return opt.metavar
return None
def auto_complete_paths(current, completion_type):
# type: (str, str) -> Iterable[str]
"""If ``completion_type`` is ``file`` or ``path``, list all regular files
and directories starting with ``current``; otherwise only list directories
starting with ``current``.

View File

@@ -1,4 +1,5 @@
"""Base Command class, and related routines"""
from __future__ import absolute_import, print_function
import logging
@@ -10,65 +11,75 @@ import sys
import traceback
from pip._internal.cli import cmdoptions
from pip._internal.cli.cmdoptions import make_search_scope
from pip._internal.cli.command_context import CommandContextMixIn
from pip._internal.cli.parser import (
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
ConfigOptionParser,
UpdatingDefaultsHelpFormatter,
)
from pip._internal.cli.status_codes import (
ERROR, PREVIOUS_BUILD_DIR_ERROR, SUCCESS, UNKNOWN_ERROR,
ERROR,
PREVIOUS_BUILD_DIR_ERROR,
UNKNOWN_ERROR,
VIRTUALENV_NOT_FOUND,
)
from pip._internal.download import PipSession
from pip._internal.exceptions import (
BadCommand, CommandError, InstallationError, PreviousBuildDirError,
BadCommand,
CommandError,
InstallationError,
NetworkConnectionError,
PreviousBuildDirError,
SubProcessError,
UninstallationError,
)
from pip._internal.index import PackageFinder
from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.models.target_python import TargetPython
from pip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
)
from pip._internal.req.req_file import parse_requirements
from pip._internal.utils.deprecation import deprecated
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.outdated import pip_version_check
from pip._internal.utils.temp_dir import (
global_tempdir_manager,
tempdir_registry,
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.virtualenv import running_under_virtualenv
if MYPY_CHECK_RUNNING:
from typing import Optional, List, Tuple, Any
from typing import List, Optional, Tuple, Any
from optparse import Values
from pip._internal.cache import WheelCache
from pip._internal.req.req_set import RequirementSet
from pip._internal.utils.temp_dir import (
TempDirectoryTypeRegistry as TempDirRegistry
)
__all__ = ['Command']
logger = logging.getLogger(__name__)
class Command(object):
name = None # type: Optional[str]
usage = None # type: Optional[str]
class Command(CommandContextMixIn):
usage = None # type: str
ignore_require_venv = False # type: bool
def __init__(self, isolated=False):
# type: (bool) -> None
def __init__(self, name, summary, isolated=False):
# type: (str, str, bool) -> None
super(Command, self).__init__()
parser_kw = {
'usage': self.usage,
'prog': '%s %s' % (get_prog(), self.name),
'prog': '{} {}'.format(get_prog(), name),
'formatter': UpdatingDefaultsHelpFormatter(),
'add_help_option': False,
'name': self.name,
'name': name,
'description': self.__doc__,
'isolated': isolated,
}
self.name = name
self.summary = summary
self.parser = ConfigOptionParser(**parser_kw)
self.tempdir_registry = None # type: Optional[TempDirRegistry]
# Commands should add options to this option group
optgroup_name = '%s Options' % self.name.capitalize()
optgroup_name = '{} Options'.format(self.name.capitalize())
self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name)
# Add the general options
@@ -78,69 +89,49 @@ class Command(object):
)
self.parser.add_option_group(gen_opts)
self.add_options()
def add_options(self):
# type: () -> None
pass
def handle_pip_version_check(self, options):
# type: (Values) -> None
"""
This is a no-op so that commands by default do not do the pip version
check.
"""
# Make sure we do the pip version check if the index_group options
# are present.
assert not hasattr(options, 'no_index')
def run(self, options, args):
# type: (Values, List[Any]) -> Any
# type: (Values, List[Any]) -> int
raise NotImplementedError
@classmethod
def _get_index_urls(cls, options):
"""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 url:
index_urls.append(url)
urls = getattr(options, "extra_index_urls", None)
if urls:
index_urls.extend(urls)
# Return None rather than an empty list
return index_urls or None
def _build_session(self, options, retries=None, timeout=None):
# type: (Values, Optional[int], Optional[int]) -> PipSession
session = PipSession(
cache=(
normalize_path(os.path.join(options.cache_dir, "http"))
if options.cache_dir else None
),
retries=retries if retries is not None else options.retries,
insecure_hosts=options.trusted_hosts,
index_urls=self._get_index_urls(options),
)
# Handle custom ca-bundles from the user
if options.cert:
session.verify = options.cert
# Handle SSL client certificate
if options.client_cert:
session.cert = options.client_cert
# Handle timeouts
if options.timeout or timeout:
session.timeout = (
timeout if timeout is not None else options.timeout
)
# Handle configured proxies
if options.proxy:
session.proxies = {
"http": options.proxy,
"https": options.proxy,
}
# Determine if we can prompt the user for authentication or not
session.auth.prompting = not options.no_input
return session
def parse_args(self, args):
# type: (List[str]) -> Tuple
# type: (List[str]) -> Tuple[Any, Any]
# factored out for testability
return self.parser.parse_args(args)
def main(self, args):
# type: (List[str]) -> int
try:
with self.main_context():
return self._main(args)
finally:
logging.shutdown()
def _main(self, args):
# type: (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.
self.tempdir_registry = self.enter_context(tempdir_registry())
# Intentionally set as early as possible so globally-managed temporary
# directories are available to the rest of the code.
self.enter_context(global_tempdir_manager())
options, args = self.parse_args(args)
# Set verbosity so that it can be used elsewhere.
@@ -152,19 +143,34 @@ class Command(object):
user_log_file=options.log,
)
if sys.version_info[:2] == (2, 7):
if (
sys.version_info[:2] == (2, 7) and
not options.no_python_version_warning
):
message = (
"A future version of pip will drop support for Python 2.7. "
"More details about Python 2 support in pip, can be found at "
"pip 21.0 will drop support for Python 2.7 in January 2021. "
"More details about Python 2 support in pip can be found at "
"https://pip.pypa.io/en/latest/development/release-process/#python-2-support" # noqa
)
if platform.python_implementation() == "CPython":
message = (
"Python 2.7 will reach the end of its life on January "
"Python 2.7 reached the end of its life on January "
"1st, 2020. Please upgrade your Python as Python 2.7 "
"won't be maintained after that date. "
"is no longer maintained. "
) + message
deprecated(message, replacement=None, gone_in=None)
deprecated(message, replacement=None, gone_in="21.0")
if (
sys.version_info[:2] == (3, 5) and
not options.no_python_version_warning
):
message = (
"Python 3.5 reached the end of its life on September "
"13th, 2020. Please upgrade your Python as Python 3.5 "
"is no longer maintained. pip 21.0 will drop support "
"for Python 3.5 in January 2021."
)
deprecated(message, replacement=None, gone_in="21.0")
# TODO: Try to get these passing down from the command?
# without resorting to os.environ to hold these.
@@ -184,18 +190,51 @@ class Command(object):
)
sys.exit(VIRTUALENV_NOT_FOUND)
if options.cache_dir:
options.cache_dir = normalize_path(options.cache_dir)
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 may want "
"sudo's -H flag.",
options.cache_dir,
)
options.cache_dir = None
if getattr(options, "build_dir", None):
deprecated(
reason=(
"The -b/--build/--build-dir/--build-directory "
"option is deprecated."
),
replacement=(
"use the TMPDIR/TEMP/TMP environment variable, "
"possibly combined with --no-clean"
),
gone_in="20.3",
issue=8333,
)
if 'resolver' in options.unstable_features:
logger.critical(
"--unstable-feature=resolver is no longer supported, and "
"has been replaced with --use-feature=2020-resolver instead."
)
sys.exit(ERROR)
try:
status = self.run(options, args)
# FIXME: all commands should return an exit status
# and when it is done, isinstance is not needed anymore
if isinstance(status, int):
return status
assert isinstance(status, int)
return status
except PreviousBuildDirError as exc:
logger.critical(str(exc))
logger.debug('Exception information:', exc_info=True)
return PREVIOUS_BUILD_DIR_ERROR
except (InstallationError, UninstallationError, BadCommand) as exc:
except (InstallationError, UninstallationError, BadCommand,
SubProcessError, NetworkConnectionError) as exc:
logger.critical(str(exc))
logger.debug('Exception information:', exc_info=True)
@@ -223,124 +262,4 @@ class Command(object):
return UNKNOWN_ERROR
finally:
allow_version_check = (
# Does this command have the index_group options?
hasattr(options, "no_index") and
# Is this command allowed to perform this check?
not (options.disable_pip_version_check or options.no_index)
)
# Check if we're using the latest version of pip available
if allow_version_check:
session = self._build_session(
options,
retries=0,
timeout=min(5, options.timeout)
)
with session:
pip_version_check(session, options)
# Shutdown the logging module
logging.shutdown()
return SUCCESS
class RequirementCommand(Command):
@staticmethod
def populate_requirement_set(requirement_set, # type: RequirementSet
args, # type: List[str]
options, # type: Values
finder, # type: PackageFinder
session, # type: PipSession
name, # type: str
wheel_cache # type: Optional[WheelCache]
):
# type: (...) -> None
"""
Marshal cmd line args into a requirement set.
"""
# NOTE: As a side-effect, options.require_hashes and
# requirement_set.require_hashes may be updated
for filename in options.constraints:
for req_to_add in parse_requirements(
filename,
constraint=True, finder=finder, options=options,
session=session, wheel_cache=wheel_cache):
req_to_add.is_direct = True
requirement_set.add_requirement(req_to_add)
for req in args:
req_to_add = install_req_from_line(
req, None, isolated=options.isolated_mode,
use_pep517=options.use_pep517,
wheel_cache=wheel_cache
)
req_to_add.is_direct = True
requirement_set.add_requirement(req_to_add)
for req in options.editables:
req_to_add = install_req_from_editable(
req,
isolated=options.isolated_mode,
use_pep517=options.use_pep517,
wheel_cache=wheel_cache
)
req_to_add.is_direct = True
requirement_set.add_requirement(req_to_add)
for filename in options.requirements:
for req_to_add in parse_requirements(
filename,
finder=finder, options=options, session=session,
wheel_cache=wheel_cache,
use_pep517=options.use_pep517):
req_to_add.is_direct = True
requirement_set.add_requirement(req_to_add)
# If --require-hashes was a line in a requirements file, tell
# RequirementSet about it:
requirement_set.require_hashes = options.require_hashes
if not (args or options.editables or options.requirements):
opts = {'name': name}
if options.find_links:
raise CommandError(
'You must give at least one requirement to %(name)s '
'(maybe you meant "pip %(name)s %(links)s"?)' %
dict(opts, links=' '.join(options.find_links)))
else:
raise CommandError(
'You must give at least one requirement to %(name)s '
'(see "pip help %(name)s")' % opts)
def _build_package_finder(
self,
options, # type: Values
session, # type: PipSession
target_python=None, # type: Optional[TargetPython]
ignore_requires_python=None, # type: Optional[bool]
):
# type: (...) -> PackageFinder
"""
Create a package finder appropriate to this requirement command.
:param ignore_requires_python: Whether to ignore incompatible
"Requires-Python" values in links. Defaults to False.
"""
search_scope = make_search_scope(options)
selection_prefs = SelectionPreferences(
allow_yanked=True,
format_control=options.format_control,
allow_all_prereleases=options.pre,
prefer_binary=options.prefer_binary,
ignore_requires_python=ignore_requires_python,
)
return PackageFinder.create(
search_scope=search_scope,
selection_prefs=selection_prefs,
trusted_hosts=options.trusted_hosts,
session=session,
target_python=target_python,
)
self.handle_pip_version_check(options)

View File

@@ -5,11 +5,14 @@ The principle here is to define options once, but *not* instantiate them
globally. One reason being that options with action='append' can carry state
between parses. pip parses general options twice internally, and shouldn't
pass on state. To be consistent, all options will follow this design.
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import logging
import os
import textwrap
import warnings
from distutils.util import strtobool
@@ -17,26 +20,23 @@ from functools import partial
from optparse import SUPPRESS_HELP, Option, OptionGroup
from textwrap import dedent
from pip._internal.cli.progress_bars import BAR_TYPES
from pip._internal.exceptions import CommandError
from pip._internal.locations import USER_CACHE_DIR, get_src_prefix
from pip._internal.models.format_control import FormatControl
from pip._internal.models.index import PyPI
from pip._internal.models.search_scope import SearchScope
from pip._internal.models.target_python import TargetPython
from pip._internal.utils.hashes import STRONG_HASHES
from pip._internal.utils.misc import redact_password_from_url
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.ui import BAR_TYPES
if MYPY_CHECK_RUNNING:
from typing import Any, Callable, Dict, Optional, Tuple
from optparse import OptionParser, Values
from pip._internal.cli.parser import ConfigOptionParser
logger = logging.getLogger(__name__)
def raise_option_error(parser, option, msg):
# type: (OptionParser, Option, str) -> None
"""
Raise an option parsing error using parser.error().
@@ -75,14 +75,15 @@ def check_install_build_global(options, check_options=None):
check_options = options
def getname(n):
# type: (str) -> Optional[Any]
return getattr(check_options, n, None)
names = ["build_options", "global_options", "install_options"]
if any(map(getname, names)):
control = options.format_control
control.disallow_binaries()
warnings.warn(
'Disabling all use of wheels due to the use of --build-options '
'/ --global-options / --install-options.', stacklevel=2,
'Disabling all use of wheels due to the use of --build-option '
'/ --global-option / --install-option.', stacklevel=2,
)
@@ -126,6 +127,17 @@ def check_dist_restriction(options, check_target=False):
)
def _path_option_check(option, opt, value):
# type: (Option, str, str) -> str
return os.path.expanduser(value)
class PipOption(Option):
TYPES = Option.TYPES + ("path",)
TYPE_CHECKER = Option.TYPE_CHECKER.copy()
TYPE_CHECKER["path"] = _path_option_check
###########
# options #
###########
@@ -213,10 +225,11 @@ progress_bar = partial(
) # type: Callable[..., Option]
log = partial(
Option,
PipOption,
"--log", "--log-file", "--local-log",
dest="log",
metavar="path",
type="path",
help="Path to a verbose appending log."
) # type: Callable[..., Option]
@@ -227,7 +240,7 @@ no_input = partial(
dest='no_input',
action='store_true',
default=False,
help=SUPPRESS_HELP
help="Disable prompting for input."
) # type: Callable[..., Option]
proxy = partial(
@@ -259,16 +272,6 @@ timeout = partial(
help='Set the socket timeout (default %default seconds).',
) # type: Callable[..., Option]
skip_requirements_regex = partial(
Option,
# A regex to be used to skip requirements
'--skip-requirements-regex',
dest='skip_requirements_regex',
type='str',
default='',
help=SUPPRESS_HELP,
) # type: Callable[..., Option]
def exists_action():
# type: () -> Option
@@ -287,19 +290,19 @@ def exists_action():
cert = partial(
Option,
PipOption,
'--cert',
dest='cert',
type='str',
type='path',
metavar='path',
help="Path to alternate CA bundle.",
) # type: Callable[..., Option]
client_cert = partial(
Option,
PipOption,
'--client-cert',
dest='client_cert',
type='str',
type='path',
default=None,
metavar='path',
help="Path to SSL client certificate, a single file containing the "
@@ -320,6 +323,7 @@ index_url = partial(
def extra_index_url():
# type: () -> Option
return Option(
'--extra-index-url',
dest='extra_index_urls',
@@ -350,36 +354,14 @@ def find_links():
action='append',
default=[],
metavar='url',
help="If a url or path to an html file, then parse for links to "
"archives. If a local path or file:// url that's a directory, "
"then look for archives in the directory listing.",
help="If a URL or path to an html file, then parse for links to "
"archives such as sdist (.tar.gz) or wheel (.whl) files. "
"If a local path or file:// URL that's a directory, "
"then look for archives in the directory listing. "
"Links to VCS project URLs are not supported.",
)
def make_search_scope(options, suppress_no_index=False):
# type: (Values, bool) -> SearchScope
"""
:param suppress_no_index: Whether to ignore the --no-index option
when constructing the SearchScope object.
"""
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_password_from_url(url) for url in index_urls),
)
index_urls = []
# Make sure find_links is a list before passing to create().
find_links = options.find_links or []
search_scope = SearchScope.create(
find_links=find_links, index_urls=index_urls,
)
return search_scope
def trusted_host():
# type: () -> Option
return Option(
@@ -388,8 +370,8 @@ def trusted_host():
action="append",
metavar="HOSTNAME",
default=[],
help="Mark this host as trusted, even though it does not have valid "
"or any HTTPS.",
help="Mark this host or host:port pair as trusted, even though it "
"does not have valid or any HTTPS.",
)
@@ -432,12 +414,21 @@ def editable():
)
def _handle_src(option, opt_str, value, parser):
# type: (Option, str, str, OptionParser) -> None
value = os.path.abspath(value)
setattr(parser.values, option.dest, value)
src = partial(
Option,
PipOption,
'--src', '--source', '--source-dir', '--source-directory',
dest='src_dir',
type='path',
metavar='dir',
default=get_src_prefix(),
action='callback',
callback=_handle_src,
help='Directory to check out editable projects into. '
'The default in a virtualenv is "<venv path>/src". '
'The default for global installs is "<current dir>/src".'
@@ -473,12 +464,12 @@ def no_binary():
"--no-binary", dest="format_control", action="callback",
callback=_handle_no_binary, type="str",
default=format_control,
help="Do not use binary packages. Can be supplied multiple times, and "
"each time adds to the existing value. Accepts either :all: to "
"disable all binary packages, :none: to empty the set, or one or "
"more package names with commas between them. Note that some "
"packages are tricky to compile and may fail to install when "
"this option is used on them.",
help='Do not use binary packages. Can be supplied multiple times, and '
'each time adds to the existing value. Accepts either ":all:" to '
'disable all binary packages, ":none:" to empty the set (notice '
'the colons), or one or more package names with commas between '
'them (no colons). Note that some packages are tricky to compile '
'and may fail to install when this option is used on them.',
)
@@ -489,12 +480,12 @@ def only_binary():
"--only-binary", dest="format_control", action="callback",
callback=_handle_only_binary, type="str",
default=format_control,
help="Do not use source packages. Can be supplied multiple times, and "
"each time adds to the existing value. Accepts either :all: to "
"disable all source packages, :none: to empty the set, or one or "
"more package names with commas between them. Packages without "
"binary distributions will fail to install when this option is "
"used on them.",
help='Do not use source packages. Can be supplied multiple times, and '
'each time adds to the existing value. Accepts either ":all:" to '
'disable all source packages, ":none:" to empty the set, or one '
'or more package names with commas between them. Packages '
'without binary distributions will fail to install when this '
'option is used on them.',
)
@@ -636,11 +627,12 @@ def prefer_binary():
cache_dir = partial(
Option,
PipOption,
"--cache-dir",
dest="cache_dir",
default=USER_CACHE_DIR,
metavar="dir",
type='path',
help="Store the cache data in <dir>."
) # type: Callable[..., Option]
@@ -691,12 +683,24 @@ no_deps = partial(
help="Don't install package dependencies.",
) # type: Callable[..., Option]
def _handle_build_dir(option, opt, value, parser):
# type: (Option, str, str, OptionParser) -> None
if value:
value = os.path.abspath(value)
setattr(parser.values, option.dest, value)
build_dir = partial(
Option,
PipOption,
'-b', '--build', '--build-dir', '--build-directory',
dest='build_dir',
type='path',
metavar='dir',
help='Directory to unpack packages into and build in. Note that '
action='callback',
callback=_handle_build_dir,
help='(DEPRECATED) '
'Directory to unpack packages into and build in. Note that '
'an initial build still takes place in a temporary directory. '
'The location of temporary directories can be controlled by setting '
'the TMPDIR environment variable (TEMP on Windows) appropriately. '
@@ -818,16 +822,6 @@ disable_pip_version_check = partial(
) # type: Callable[..., Option]
# Deprecated, Remove later
always_unzip = partial(
Option,
'-Z', '--always-unzip',
dest='always_unzip',
action='store_true',
help=SUPPRESS_HELP,
) # type: Callable[..., Option]
def _handle_merge_hash(option, opt_str, value, parser):
# type: (Option, str, str, OptionParser) -> None
"""Given a value spelled "algo:digest", append the digest to a list
@@ -837,12 +831,12 @@ def _handle_merge_hash(option, opt_str, value, parser):
try:
algo, digest = value.split(':', 1)
except ValueError:
parser.error('Arguments to %s must be a hash name '
'followed by a value, like --hash=sha256:abcde...' %
opt_str)
parser.error('Arguments to {} must be a hash name ' # noqa
'followed by a value, like --hash=sha256:'
'abcde...'.format(opt_str))
if algo not in STRONG_HASHES:
parser.error('Allowed hash algorithms for %s are %s.' %
(opt_str, ', '.join(STRONG_HASHES)))
parser.error('Allowed hash algorithms for {} are {}.'.format( # noqa
opt_str, ', '.join(STRONG_HASHES)))
parser.values.hashes.setdefault(algo, []).append(digest)
@@ -873,9 +867,10 @@ require_hashes = partial(
list_path = partial(
Option,
PipOption,
'--path',
dest='path',
type='path',
action='append',
help='Restrict to the specified installation path for listing '
'packages (can be used multiple times).'
@@ -890,6 +885,52 @@ def check_list_path_option(options):
)
no_python_version_warning = partial(
Option,
'--no-python-version-warning',
dest='no_python_version_warning',
action='store_true',
default=False,
help='Silence deprecation warnings for upcoming unsupported Pythons.',
) # type: Callable[..., Option]
unstable_feature = partial(
Option,
'--unstable-feature',
dest='unstable_features',
metavar='feature',
action='append',
default=[],
choices=['resolver'],
help=SUPPRESS_HELP, # TODO: drop this in pip 20.3
) # type: Callable[..., Option]
use_new_feature = partial(
Option,
'--use-feature',
dest='features_enabled',
metavar='feature',
action='append',
default=[],
choices=['2020-resolver', 'fast-deps'],
help='Enable new functionality, that may be backward incompatible.',
) # type: Callable[..., Option]
use_deprecated_feature = partial(
Option,
'--use-deprecated',
dest='deprecated_features_enabled',
metavar='feature',
action='append',
default=[],
choices=[],
help=(
'Enable deprecated functionality, that will be removed in the future.'
),
) # type: Callable[..., Option]
##########
# groups #
##########
@@ -908,7 +949,6 @@ general_group = {
proxy,
retries,
timeout,
skip_requirements_regex,
exists_action,
trusted_host,
cert,
@@ -917,6 +957,10 @@ general_group = {
no_cache,
disable_pip_version_check,
no_color,
no_python_version_warning,
unstable_feature,
use_new_feature,
use_deprecated_feature,
]
} # type: Dict[str, Any]

View File

@@ -6,11 +6,10 @@ import sys
from pip._internal.cli import cmdoptions
from pip._internal.cli.parser import (
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
)
from pip._internal.commands import (
commands_dict, get_similar_commands, get_summaries,
ConfigOptionParser,
UpdatingDefaultsHelpFormatter,
)
from pip._internal.commands import commands_dict, get_similar_commands
from pip._internal.exceptions import CommandError
from pip._internal.utils.misc import get_pip_version, get_prog
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@@ -48,8 +47,10 @@ def create_main_parser():
parser.main = True # type: ignore
# create command listing for description
command_summaries = get_summaries()
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
description = [''] + [
'{name:27} {command_info.summary}'.format(**locals())
for name, command_info in commands_dict.items()
]
parser.description = '\n'.join(description)
return parser
@@ -85,9 +86,9 @@ def parse_command(args):
if cmd_name not in commands_dict:
guess = get_similar_commands(cmd_name)
msg = ['unknown command "%s"' % cmd_name]
msg = ['unknown command "{}"'.format(cmd_name)]
if guess:
msg.append('maybe you meant "%s"' % guess)
msg.append('maybe you meant "{}"'.format(guess))
raise CommandError(' - '.join(msg))

View File

@@ -1,4 +1,8 @@
"""Base option parser setup"""
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
from __future__ import absolute_import
import logging
@@ -27,14 +31,14 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
optparse.IndentedHelpFormatter.__init__(self, *args, **kwargs)
def format_option_strings(self, option):
return self._format_option_strings(option, ' <%s>', ', ')
return self._format_option_strings(option)
def _format_option_strings(self, option, mvarfmt=' <%s>', optsep=', '):
def _format_option_strings(self, option, mvarfmt=' <{}>', optsep=', '):
"""
Return a comma-separated list of option strings and metavars.
:param option: tuple of (short opt, long opt), e.g: ('-f', '--format')
:param mvarfmt: metavar format string - evaluated as mvarfmt % metavar
:param mvarfmt: metavar format string
:param optsep: separator
"""
opts = []
@@ -48,7 +52,7 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
if option.takes_value():
metavar = option.metavar or option.dest.lower()
opts.append(mvarfmt % metavar.lower())
opts.append(mvarfmt.format(metavar.lower()))
return ''.join(opts)
@@ -62,7 +66,8 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
Ensure there is only one newline between usage and the first heading
if there is no description.
"""
msg = '\nUsage: %s\n' % self.indent_lines(textwrap.dedent(usage), " ")
msg = '\nUsage: {}\n'.format(
self.indent_lines(textwrap.dedent(usage), " "))
return msg
def format_description(self, description):
@@ -78,7 +83,7 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
description = description.rstrip()
# dedent, then reindent
description = self.indent_lines(textwrap.dedent(description), " ")
description = '%s:\n%s\n' % (label, description)
description = '{}:\n{}\n'.format(label, description)
return description
else:
return ''
@@ -146,7 +151,7 @@ class ConfigOptionParser(CustomOptionParser):
try:
return option.check_value(key, val)
except optparse.OptionValueError as exc:
print("An error occurred during configuration: %s" % exc)
print("An error occurred during configuration: {}".format(exc))
sys.exit(3)
def _get_ordered_configuration_items(self):
@@ -245,7 +250,7 @@ class ConfigOptionParser(CustomOptionParser):
def error(self, msg):
self.print_usage(sys.stderr)
self.exit(UNKNOWN_ERROR, "%s\n" % msg)
self.exit(UNKNOWN_ERROR, "{}\n".format(msg))
def invalid_config_error_message(action, key, val):

View File

@@ -1,59 +1,111 @@
"""
Package containing all pip commands
"""
# The following comment should be removed at some point in the future.
# mypy: disallow-untyped-defs=False
# There is currently a bug in python/typeshed mentioned at
# https://github.com/python/typeshed/issues/3906 which causes the
# return type of difflib.get_close_matches to be reported
# as List[Sequence[str]] whereas it should have been List[str]
from __future__ import absolute_import
from pip._internal.commands.completion import CompletionCommand
from pip._internal.commands.configuration import ConfigurationCommand
from pip._internal.commands.debug import DebugCommand
from pip._internal.commands.download import DownloadCommand
from pip._internal.commands.freeze import FreezeCommand
from pip._internal.commands.hash import HashCommand
from pip._internal.commands.help import HelpCommand
from pip._internal.commands.list import ListCommand
from pip._internal.commands.check import CheckCommand
from pip._internal.commands.search import SearchCommand
from pip._internal.commands.show import ShowCommand
from pip._internal.commands.install import InstallCommand
from pip._internal.commands.uninstall import UninstallCommand
from pip._internal.commands.wheel import WheelCommand
import importlib
from collections import OrderedDict, namedtuple
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import List, Type
from typing import Any
from pip._internal.cli.base_command import Command
commands_order = [
InstallCommand,
DownloadCommand,
UninstallCommand,
FreezeCommand,
ListCommand,
ShowCommand,
CheckCommand,
ConfigurationCommand,
SearchCommand,
WheelCommand,
HashCommand,
CompletionCommand,
DebugCommand,
HelpCommand,
] # type: List[Type[Command]]
commands_dict = {c.name: c for c in commands_order}
CommandInfo = namedtuple('CommandInfo', 'module_path, class_name, summary')
# The ordering matters for help display.
# Also, even though the module path starts with the same
# "pip._internal.commands" prefix in each case, we include the full path
# because it makes testing easier (specifically when modifying commands_dict
# in test setup / teardown by adding info for a FakeCommand class defined
# in a test-related module).
# Finally, we need to pass an iterable of pairs here rather than a dict
# so that the ordering won't be lost when using Python 2.7.
commands_dict = OrderedDict([
('install', CommandInfo(
'pip._internal.commands.install', 'InstallCommand',
'Install packages.',
)),
('download', CommandInfo(
'pip._internal.commands.download', 'DownloadCommand',
'Download packages.',
)),
('uninstall', CommandInfo(
'pip._internal.commands.uninstall', 'UninstallCommand',
'Uninstall packages.',
)),
('freeze', CommandInfo(
'pip._internal.commands.freeze', 'FreezeCommand',
'Output installed packages in requirements format.',
)),
('list', CommandInfo(
'pip._internal.commands.list', 'ListCommand',
'List 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.',
)),
('config', CommandInfo(
'pip._internal.commands.configuration', 'ConfigurationCommand',
'Manage local and global configuration.',
)),
('search', CommandInfo(
'pip._internal.commands.search', 'SearchCommand',
'Search PyPI for packages.',
)),
('cache', CommandInfo(
'pip._internal.commands.cache', 'CacheCommand',
"Inspect and manage pip's wheel cache.",
)),
('wheel', CommandInfo(
'pip._internal.commands.wheel', 'WheelCommand',
'Build wheels from your requirements.',
)),
('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.',
)),
('debug', CommandInfo(
'pip._internal.commands.debug', 'DebugCommand',
'Show information useful for debugging.',
)),
('help', CommandInfo(
'pip._internal.commands.help', 'HelpCommand',
'Show help for commands.',
)),
]) # type: OrderedDict[str, CommandInfo]
def get_summaries(ordered=True):
"""Yields sorted (command name, command summary) tuples."""
def create_command(name, **kwargs):
# type: (str, **Any) -> Command
"""
Create an instance of the Command class with the given name.
"""
module_path, class_name, summary = commands_dict[name]
module = importlib.import_module(module_path)
command_class = getattr(module, class_name)
command = command_class(name=name, summary=summary, **kwargs)
if ordered:
cmditems = _sort_commands(commands_dict, commands_order)
else:
cmditems = commands_dict.items()
for name, command_class in cmditems:
yield (name, command_class.summary)
return command
def get_similar_commands(name):
@@ -68,14 +120,3 @@ def get_similar_commands(name):
return close_commands[0]
else:
return False
def _sort_commands(cmddict, order):
def keyfn(key):
try:
return order.index(key[1])
except ValueError:
# unordered items should come last
return 0xff
return sorted(cmddict.items(), key=keyfn)

View File

@@ -1,28 +1,37 @@
import logging
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,
check_package_set,
create_package_set_from_installed,
)
from pip._internal.utils.misc import write_output
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
logger = logging.getLogger(__name__)
if MYPY_CHECK_RUNNING:
from typing import List, Any
from optparse import Values
class CheckCommand(Command):
"""Verify installed packages have compatible dependencies."""
name = 'check'
usage = """
%prog [options]"""
summary = 'Verify installed packages have compatible dependencies.'
def run(self, options, args):
# type: (Values, List[Any]) -> int
package_set, parsing_probs = create_package_set_from_installed()
missing, conflicting = check_package_set(package_set)
for project_name in missing:
version = package_set[project_name].version
for dependency in missing[project_name]:
logger.info(
write_output(
"%s %s requires %s, which is not installed.",
project_name, version, dependency[0],
)
@@ -30,12 +39,13 @@ class CheckCommand(Command):
for project_name in conflicting:
version = package_set[project_name].version
for dep_name, dep_version, req in conflicting[project_name]:
logger.info(
write_output(
"%s %s has requirement %s, but you have %s %s.",
project_name, version, req, dep_name, dep_version,
)
if missing or conflicting or parsing_probs:
return 1
return ERROR
else:
logger.info("No broken requirements found.")
write_output("No broken requirements found.")
return SUCCESS

View File

@@ -4,32 +4,38 @@ import sys
import textwrap
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.utils.misc import get_prog
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import List
from optparse import Values
BASE_COMPLETION = """
# pip %(shell)s completion start%(script)s# pip %(shell)s completion end
# pip {shell} completion start{script}# pip {shell} completion end
"""
COMPLETION_SCRIPTS = {
'bash': """
_pip_completion()
{
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
{{
COMPREPLY=( $( COMP_WORDS="${{COMP_WORDS[*]}}" \\
COMP_CWORD=$COMP_CWORD \\
PIP_AUTO_COMPLETE=1 $1 ) )
}
complete -o default -F _pip_completion %(prog)s
PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) )
}}
complete -o default -F _pip_completion {prog}
""",
'zsh': """
function _pip_completion {
function _pip_completion {{
local words cword
read -Ac words
read -cn cword
reply=( $( COMP_WORDS="$words[*]" \\
COMP_CWORD=$(( cword-1 )) \\
PIP_AUTO_COMPLETE=1 $words[1] ) )
}
compctl -K _pip_completion %(prog)s
PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null ))
}}
compctl -K _pip_completion {prog}
""",
'fish': """
function __fish_complete_pip
@@ -40,55 +46,53 @@ COMPLETION_SCRIPTS = {
set -lx PIP_AUTO_COMPLETE 1
string split \\ -- (eval $COMP_WORDS[1])
end
complete -fa "(__fish_complete_pip)" -c %(prog)s
complete -fa "(__fish_complete_pip)" -c {prog}
""",
}
class CompletionCommand(Command):
"""A helper command to be used for command completion."""
name = 'completion'
summary = 'A helper command used for command completion.'
ignore_require_venv = True
def __init__(self, *args, **kw):
super(CompletionCommand, self).__init__(*args, **kw)
cmd_opts = self.cmd_opts
cmd_opts.add_option(
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'--bash', '-b',
action='store_const',
const='bash',
dest='shell',
help='Emit completion code for bash')
cmd_opts.add_option(
self.cmd_opts.add_option(
'--zsh', '-z',
action='store_const',
const='zsh',
dest='shell',
help='Emit completion code for zsh')
cmd_opts.add_option(
self.cmd_opts.add_option(
'--fish', '-f',
action='store_const',
const='fish',
dest='shell',
help='Emit completion code for fish')
self.parser.insert_option_group(0, cmd_opts)
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
# type: (Values, List[str]) -> int
"""Prints the completion code of the given shell"""
shells = COMPLETION_SCRIPTS.keys()
shell_options = ['--' + shell for shell in sorted(shells)]
if options.shell in shells:
script = textwrap.dedent(
COMPLETION_SCRIPTS.get(options.shell, '') % {
'prog': get_prog(),
}
COMPLETION_SCRIPTS.get(options.shell, '').format(
prog=get_prog())
)
print(BASE_COMPLETION % {'script': script, 'shell': options.shell})
print(BASE_COMPLETION.format(script=script, shell=options.shell))
return SUCCESS
else:
sys.stderr.write(
'ERROR: You must pass %s\n' % ' or '.join(shell_options)
'ERROR: You must pass {}\n' .format(' or '.join(shell_options))
)
return SUCCESS

View File

@@ -5,34 +5,44 @@ import subprocess
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import ERROR, SUCCESS
from pip._internal.configuration import (
Configuration, get_configuration_files, kinds,
Configuration,
get_configuration_files,
kinds,
)
from pip._internal.exceptions import PipError
from pip._internal.utils.deprecation import deprecated
from pip._internal.utils.misc import get_prog
from pip._internal.utils.virtualenv import running_under_virtualenv
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import get_prog, write_output
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import List, Any, Optional
from optparse import Values
from pip._internal.configuration import Kind
logger = logging.getLogger(__name__)
class ConfigurationCommand(Command):
"""Manage local and global configuration.
"""
Manage local and global configuration.
Subcommands:
Subcommands:
list: List the active configuration (or from the file specified)
edit: Edit the configuration file in an editor
get: Get the value associated with name
set: Set the name=value
unset: Unset the value associated with name
- list: List the active configuration (or from the file specified)
- edit: Edit the configuration file in an editor
- get: Get the value associated with name
- set: Set the name=value
- unset: Unset the value associated with name
- debug: List the configuration files and values defined under them
If none of --user, --global and --site are passed, a virtual
environment configuration file is used if one is active and the file
exists. Otherwise, all modifications happen on the to the user file by
default.
If none of --user, --global and --site are passed, a virtual
environment configuration file is used if one is active and the file
exists. Otherwise, all modifications happen on the to the user file by
default.
"""
name = 'config'
ignore_require_venv = True
usage = """
%prog [<file-option>] list
%prog [<file-option>] [--editor <editor-path>] edit
@@ -40,15 +50,11 @@ class ConfigurationCommand(Command):
%prog [<file-option>] get name
%prog [<file-option>] set name value
%prog [<file-option>] unset name
%prog [<file-option>] debug
"""
summary = "Manage local and global configuration."
def __init__(self, *args, **kwargs):
super(ConfigurationCommand, self).__init__(*args, **kwargs)
self.configuration = None
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'--editor',
dest='editor',
@@ -84,32 +90,24 @@ class ConfigurationCommand(Command):
help='Use the current environment configuration file only'
)
self.cmd_opts.add_option(
'--venv',
dest='venv_file',
action='store_true',
default=False,
help=(
'[Deprecated] Use the current environment configuration '
'file in a virtual environment only'
)
)
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
# type: (Values, 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
"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 ({}) to perform.".format(
", ".join(sorted(handlers)))
logger.error(
"Need an action (%s) to perform.",
", ".join(sorted(handlers)),
)
return ERROR
@@ -141,21 +139,7 @@ class ConfigurationCommand(Command):
return SUCCESS
def _determine_file(self, options, need_value):
# Convert legacy venv_file option to site_file or error
if options.venv_file and not options.site_file:
if running_under_virtualenv():
options.site_file = True
deprecated(
"The --venv option has been deprecated.",
replacement="--site",
gone_in="19.3",
)
else:
raise PipError(
"Legacy --venv option requires a virtual environment. "
"Use --site instead."
)
# type: (Values, bool) -> Optional[Kind]
file_options = [key for key, value in (
(kinds.USER, options.user_file),
(kinds.GLOBAL, options.global_file),
@@ -182,30 +166,70 @@ class ConfigurationCommand(Command):
)
def list_values(self, options, args):
# type: (Values, List[str]) -> None
self._get_n_args(args, "list", n=0)
for key, value in sorted(self.configuration.items()):
logger.info("%s=%r", key, value)
write_output("%s=%r", key, value)
def get_name(self, options, args):
# type: (Values, List[str]) -> None
key = self._get_n_args(args, "get [name]", n=1)
value = self.configuration.get_value(key)
logger.info("%s", value)
write_output("%s", value)
def set_name_value(self, options, args):
# type: (Values, 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, args):
# type: (Values, 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, args):
# type: (Values, List[str]) -> None
"""List config key-value pairs across different config files"""
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)
for fname in files:
with indent_log():
file_exists = os.path.exists(fname)
write_output("%s, exists: %r",
fname, file_exists)
if file_exists:
self.print_config_file_values(variant)
def print_config_file_values(self, variant):
# type: (Kind) -> None
"""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)
def print_env_var_values(self):
# type: () -> None
"""Get key-values pairs present as environment variables"""
write_output("%s:", 'env_var')
with indent_log():
for key, value in sorted(self.configuration.get_environ_vars()):
env_var = 'PIP_{}'.format(key.upper())
write_output("%s=%r", env_var, value)
def open_in_editor(self, options, args):
# type: (Values, List[str]) -> None
editor = self._determine_editor(options)
fname = self.configuration.get_file_to_edit()
@@ -221,6 +245,7 @@ class ConfigurationCommand(Command):
)
def _get_n_args(self, args, example, n):
# type: (List[str], str, int) -> Any
"""Helper to make sure the command got the right number of arguments
"""
if len(args) != n:
@@ -236,18 +261,19 @@ class ConfigurationCommand(Command):
return args
def _save_configuration(self):
# type: () -> None
# We successfully ran a modifying command. Need to save the
# configuration.
try:
self.configuration.save()
except Exception:
logger.error(
"Unable to save configuration. Please report this as a bug.",
exc_info=1
logger.exception(
"Unable to save configuration. Please report this as a bug."
)
raise PipError("Internal Error.")
def _determine_editor(self, options):
# type: (Values) -> str
if options.editor is not None:
return options.editor
elif "VISUAL" in os.environ:

View File

@@ -2,8 +2,14 @@ from __future__ import absolute_import
import locale
import logging
import os
import sys
import pip._vendor
from pip._vendor import pkg_resources
from pip._vendor.certifi import where
from pip import __file__ as pip_location
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
from pip._internal.cli.cmdoptions import make_target_python
@@ -11,18 +17,19 @@ from pip._internal.cli.status_codes import SUCCESS
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import get_pip_version
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.wheel import format_tag
if MYPY_CHECK_RUNNING:
from typing import Any, List
from types import ModuleType
from typing import List, Optional, Dict
from optparse import Values
from pip._internal.configuration import Configuration
logger = logging.getLogger(__name__)
def show_value(name, value):
# type: (str, str) -> None
logger.info('{}: {}'.format(name, value))
# type: (str, Optional[str]) -> None
logger.info('%s: %s', name, value)
def show_sys_implementation():
@@ -38,6 +45,88 @@ def show_sys_implementation():
show_value('name', implementation_name)
def create_vendor_txt_map():
# type: () -> Dict[str, str]
vendor_txt_path = os.path.join(
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]
# Transform into "module" -> version dict.
return dict(line.split('==', 1) for line in lines) # type: ignore
def get_module_from_module_name(module_name):
# type: (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'
__import__(
'pip._vendor.{}'.format(module_name),
globals(),
locals(),
level=0
)
return getattr(pip._vendor, module_name)
def get_vendor_version_from_module(module_name):
# type: (str) -> Optional[str]
module = get_module_from_module_name(module_name)
version = getattr(module, '__version__', None)
if not version:
# Try to find version in debundled module info
# The type for module.__file__ is Optional[str] in
# Python 2, and str in Python 3. The type: ignore is
# added to account for Python 2, instead of a cast
# and should be removed once we drop Python 2 support
pkg_set = pkg_resources.WorkingSet(
[os.path.dirname(module.__file__)] # type: ignore
)
package = pkg_set.find(pkg_resources.Requirement.parse(module_name))
version = getattr(package, 'version', None)
return version
def show_actual_vendor_versions(vendor_txt_versions):
# type: (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 = ''
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)'
actual_version = expected_version
elif actual_version != expected_version:
extra_message = ' (CONFLICT: vendor.txt suggests version should'\
' be {})'.format(expected_version)
logger.info('%s==%s%s', module_name, actual_version, extra_message)
def show_vendor_versions():
# type: () -> None
logger.info('vendored library versions:')
vendor_txt_versions = create_vendor_txt_map()
with indent_log():
show_actual_vendor_versions(vendor_txt_versions)
def show_tags(options):
# type: (Values) -> None
tag_limit = 10
@@ -62,7 +151,7 @@ def show_tags(options):
with indent_log():
for tag in tags:
logger.info(format_tag(tag))
logger.info(str(tag))
if tags_limited:
msg = (
@@ -72,26 +161,44 @@ def show_tags(options):
logger.info(msg)
def ca_bundle_info(config):
# type: (Configuration) -> str
levels = set()
for key, _ in config.items():
levels.add(key.split('.')[0])
if not levels:
return "Not specified"
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'
if 'global' in levels:
levels.remove('global')
return ", ".join(levels)
class DebugCommand(Command):
"""
Display debug information.
"""
name = 'debug'
usage = """
%prog <options>"""
summary = 'Show information useful for debugging.'
ignore_require_venv = True
def __init__(self, *args, **kw):
super(DebugCommand, self).__init__(*args, **kw)
cmd_opts = self.cmd_opts
cmdoptions.add_target_python_options(cmd_opts)
self.parser.insert_option_group(0, cmd_opts)
def add_options(self):
# type: () -> None
cmdoptions.add_target_python_options(self.cmd_opts)
self.parser.insert_option_group(0, self.cmd_opts)
self.parser.config.load()
def run(self, options, args):
# type: (Values, List[Any]) -> int
# type: (Values, List[str]) -> int
logger.warning(
"This command is only meant for debugging. "
"Do not use this with automation for parsing and getting these "
@@ -109,6 +216,14 @@ class DebugCommand(Command):
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_vendor_versions()
show_tags(options)
return SUCCESS

View File

@@ -4,15 +4,17 @@ import logging
import os
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import RequirementCommand
from pip._internal.cli.cmdoptions import make_target_python
from pip._internal.legacy_resolve import Resolver
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req import RequirementSet
from pip._internal.req.req_tracker import RequirementTracker
from pip._internal.utils.filesystem import check_path_owner
from pip._internal.utils.misc import ensure_dir, normalize_path
from pip._internal.cli.req_command import RequirementCommand, 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.temp_dir import TempDirectory
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List
logger = logging.getLogger(__name__)
@@ -29,7 +31,6 @@ class DownloadCommand(RequirementCommand):
pip also supports downloading from "requirements files", which provide
an easy way to specify a whole environment to be downloaded.
"""
name = 'download'
usage = """
%prog [options] <requirement specifier> [package-index-options] ...
@@ -38,31 +39,25 @@ class DownloadCommand(RequirementCommand):
%prog [options] <local project path> ...
%prog [options] <archive url/path> ..."""
summary = 'Download packages.'
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(cmdoptions.constraints())
self.cmd_opts.add_option(cmdoptions.requirements())
self.cmd_opts.add_option(cmdoptions.build_dir())
self.cmd_opts.add_option(cmdoptions.no_deps())
self.cmd_opts.add_option(cmdoptions.global_options())
self.cmd_opts.add_option(cmdoptions.no_binary())
self.cmd_opts.add_option(cmdoptions.only_binary())
self.cmd_opts.add_option(cmdoptions.prefer_binary())
self.cmd_opts.add_option(cmdoptions.src())
self.cmd_opts.add_option(cmdoptions.pre())
self.cmd_opts.add_option(cmdoptions.require_hashes())
self.cmd_opts.add_option(cmdoptions.progress_bar())
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
self.cmd_opts.add_option(cmdoptions.use_pep517())
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
def __init__(self, *args, **kw):
super(DownloadCommand, self).__init__(*args, **kw)
cmd_opts = self.cmd_opts
cmd_opts.add_option(cmdoptions.constraints())
cmd_opts.add_option(cmdoptions.requirements())
cmd_opts.add_option(cmdoptions.build_dir())
cmd_opts.add_option(cmdoptions.no_deps())
cmd_opts.add_option(cmdoptions.global_options())
cmd_opts.add_option(cmdoptions.no_binary())
cmd_opts.add_option(cmdoptions.only_binary())
cmd_opts.add_option(cmdoptions.prefer_binary())
cmd_opts.add_option(cmdoptions.src())
cmd_opts.add_option(cmdoptions.pre())
cmd_opts.add_option(cmdoptions.no_clean())
cmd_opts.add_option(cmdoptions.require_hashes())
cmd_opts.add_option(cmdoptions.progress_bar())
cmd_opts.add_option(cmdoptions.no_build_isolation())
cmd_opts.add_option(cmdoptions.use_pep517())
cmd_opts.add_option(cmdoptions.no_use_pep517())
cmd_opts.add_option(
self.cmd_opts.add_option(
'-d', '--dest', '--destination-dir', '--destination-directory',
dest='download_dir',
metavar='dir',
@@ -70,7 +65,7 @@ class DownloadCommand(RequirementCommand):
help=("Download packages into <dir>."),
)
cmdoptions.add_target_python_options(cmd_opts)
cmdoptions.add_target_python_options(self.cmd_opts)
index_opts = cmdoptions.make_option_group(
cmdoptions.index_group,
@@ -78,9 +73,12 @@ class DownloadCommand(RequirementCommand):
)
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, cmd_opts)
self.parser.insert_option_group(0, self.cmd_opts)
@with_cleanup
def run(self, options, args):
# type: (Values, List[str]) -> int
options.ignore_installed = True
# editable doesn't really make sense for `pip download`, but the bowels
# of the RequirementSet code require that property.
@@ -88,81 +86,58 @@ class DownloadCommand(RequirementCommand):
cmdoptions.check_dist_restriction(options)
options.src_dir = os.path.abspath(options.src_dir)
options.download_dir = normalize_path(options.download_dir)
ensure_dir(options.download_dir)
with self._build_session(options) as session:
target_python = make_target_python(options)
finder = self._build_package_finder(
options=options,
session=session,
target_python=target_python,
)
build_delete = (not (options.no_clean or options.build_dir))
if options.cache_dir and not check_path_owner(options.cache_dir):
logger.warning(
"The directory '%s' or its parent directory is not owned "
"by the current user and caching wheels has been "
"disabled. check the permissions and owner of that "
"directory. If executing pip with sudo, you may want "
"sudo's -H flag.",
options.cache_dir,
)
options.cache_dir = None
session = self.get_default_session(options)
with RequirementTracker() as req_tracker, TempDirectory(
options.build_dir, delete=build_delete, kind="download"
) as directory:
target_python = make_target_python(options)
finder = self._build_package_finder(
options=options,
session=session,
target_python=target_python,
)
build_delete = (not (options.no_clean or options.build_dir))
requirement_set = RequirementSet(
require_hashes=options.require_hashes,
)
self.populate_requirement_set(
requirement_set,
args,
options,
finder,
session,
self.name,
None
)
req_tracker = self.enter_context(get_requirement_tracker())
preparer = RequirementPreparer(
build_dir=directory.path,
src_dir=options.src_dir,
download_dir=options.download_dir,
wheel_download_dir=None,
progress_bar=options.progress_bar,
build_isolation=options.build_isolation,
req_tracker=req_tracker,
)
directory = TempDirectory(
options.build_dir,
delete=build_delete,
kind="download",
globally_managed=True,
)
resolver = Resolver(
preparer=preparer,
finder=finder,
session=session,
wheel_cache=None,
use_user_site=False,
upgrade_strategy="to-satisfy-only",
force_reinstall=False,
ignore_dependencies=options.ignore_dependencies,
py_version_info=options.python_version,
ignore_requires_python=False,
ignore_installed=True,
isolated=options.isolated_mode,
)
resolver.resolve(requirement_set)
reqs = self.get_requirements(args, options, finder, session)
downloaded = ' '.join([
req.name for req in requirement_set.successfully_downloaded
])
if downloaded:
logger.info('Successfully downloaded %s', downloaded)
preparer = self.make_requirement_preparer(
temp_build_dir=directory,
options=options,
req_tracker=req_tracker,
session=session,
finder=finder,
download_dir=options.download_dir,
use_user_site=False,
)
# Clean up
if not options.no_clean:
requirement_set.cleanup_files()
resolver = self.make_resolver(
preparer=preparer,
finder=finder,
options=options,
py_version_info=options.python_version,
)
return requirement_set
self.trace_basic_info(finder)
requirement_set = resolver.resolve(
reqs, check_supported_wheels=True
)
downloaded = ' '.join([req.name # type: ignore
for req in requirement_set.requirements.values()
if req.successfully_downloaded])
if downloaded:
write_output('Successfully downloaded %s', downloaded)
return SUCCESS

View File

@@ -5,12 +5,18 @@ import sys
from pip._internal.cache import WheelCache
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.models.format_control import FormatControl
from pip._internal.operations.freeze import freeze
from pip._internal.utils.compat import stdlib_pkgs
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'}
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List
class FreezeCommand(Command):
"""
@@ -18,15 +24,13 @@ class FreezeCommand(Command):
packages are listed in a case-insensitive sorted order.
"""
name = 'freeze'
usage = """
%prog [options]"""
summary = 'Output installed packages in requirements format.'
log_streams = ("ext://sys.stderr", "ext://sys.stderr")
def __init__(self, *args, **kw):
super(FreezeCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'-r', '--requirement',
dest='requirements',
@@ -63,7 +67,7 @@ class FreezeCommand(Command):
dest='freeze_all',
action='store_true',
help='Do not skip these packages in the output:'
' %s' % ', '.join(DEV_PKGS))
' {}'.format(', '.join(DEV_PKGS)))
self.cmd_opts.add_option(
'--exclude-editable',
dest='exclude_editable',
@@ -73,6 +77,7 @@ class FreezeCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
# type: (Values, List[str]) -> int
format_control = FormatControl(set(), set())
wheel_cache = WheelCache(options.cache_dir, format_control)
skip = set(stdlib_pkgs)
@@ -87,15 +92,12 @@ class FreezeCommand(Command):
local_only=options.local,
user_only=options.user,
paths=options.path,
skip_regex=options.skip_requirements_regex,
isolated=options.isolated_mode,
wheel_cache=wheel_cache,
skip=skip,
exclude_editable=options.exclude_editable,
)
try:
for line in freeze(**freeze_kwargs):
sys.stdout.write(line + '\n')
finally:
wheel_cache.cleanup()
for line in freeze(**freeze_kwargs):
sys.stdout.write(line + '\n')
return SUCCESS

View File

@@ -5,9 +5,14 @@ import logging
import sys
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import ERROR
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
from pip._internal.utils.misc import read_chunks, write_output
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List
logger = logging.getLogger(__name__)
@@ -18,37 +23,38 @@ class HashCommand(Command):
These can be used with --hash in a requirements file to do repeatable
installs.
"""
name = 'hash'
usage = '%prog [options] <file> ...'
summary = 'Compute hashes of package archives.'
ignore_require_venv = True
def __init__(self, *args, **kw):
super(HashCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'-a', '--algorithm',
dest='algorithm',
choices=STRONG_HASHES,
action='store',
default=FAVORITE_HASH,
help='The hash algorithm to use: one of %s' %
', '.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, args):
# type: (Values, List[str]) -> int
if not args:
self.parser.print_usage(sys.stderr)
return ERROR
algorithm = options.algorithm
for path in args:
logger.info('%s:\n--hash=%s:%s',
path, algorithm, _hash_of_file(path, algorithm))
write_output('%s:\n--hash=%s:%s',
path, algorithm, _hash_of_file(path, algorithm))
return SUCCESS
def _hash_of_file(path, algorithm):
# type: (str, str) -> str
"""Return the hash digest of a file."""
with open(path, 'rb') as archive:
hash = hashlib.new(algorithm)

View File

@@ -3,18 +3,25 @@ from __future__ import absolute_import
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.exceptions import CommandError
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import List
from optparse import Values
class HelpCommand(Command):
"""Show help for commands"""
name = 'help'
usage = """
%prog <command>"""
summary = 'Show help for commands.'
ignore_require_venv = True
def run(self, options, args):
from pip._internal.commands import commands_dict, get_similar_commands
# type: (Values, List[str]) -> int
from pip._internal.commands import (
commands_dict, create_command, get_similar_commands,
)
try:
# 'pip help' with no args is handled by pip.__init__.parseopt()
@@ -25,13 +32,13 @@ class HelpCommand(Command):
if cmd_name not in commands_dict:
guess = get_similar_commands(cmd_name)
msg = ['unknown command "%s"' % cmd_name]
msg = ['unknown command "{}"'.format(cmd_name)]
if guess:
msg.append('maybe you meant "%s"' % guess)
msg.append('maybe you meant "{}"'.format(guess))
raise CommandError(' - '.join(msg))
command = commands_dict[cmd_name]()
command = create_command(cmd_name)
command.parser.print_help()
return SUCCESS

View File

@@ -4,54 +4,63 @@ import json
import logging
from pip._vendor import six
from pip._vendor.six.moves import zip_longest
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
from pip._internal.cli.cmdoptions import make_search_scope
from pip._internal.cli.req_command import IndexGroupCommand
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.exceptions import CommandError
from pip._internal.index import PackageFinder
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.utils.misc import (
dist_is_editable, get_installed_distributions,
dist_is_editable,
get_installed_distributions,
tabulate,
write_output,
)
from pip._internal.utils.packaging import get_installer
from pip._internal.utils.parallel import map_multithread
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List, Set, Tuple, Iterator
from pip._internal.network.session import PipSession
from pip._vendor.pkg_resources import Distribution
logger = logging.getLogger(__name__)
class ListCommand(Command):
class ListCommand(IndexGroupCommand):
"""
List installed packages, including editables.
Packages are listed in a case-insensitive sorted order.
"""
name = 'list'
ignore_require_venv = True
usage = """
%prog [options]"""
summary = 'List installed packages.'
def __init__(self, *args, **kw):
super(ListCommand, self).__init__(*args, **kw)
cmd_opts = self.cmd_opts
cmd_opts.add_option(
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'-o', '--outdated',
action='store_true',
default=False,
help='List outdated packages')
cmd_opts.add_option(
self.cmd_opts.add_option(
'-u', '--uptodate',
action='store_true',
default=False,
help='List uptodate packages')
cmd_opts.add_option(
self.cmd_opts.add_option(
'-e', '--editable',
action='store_true',
default=False,
help='List editable projects.')
cmd_opts.add_option(
self.cmd_opts.add_option(
'-l', '--local',
action='store_true',
default=False,
@@ -64,8 +73,8 @@ class ListCommand(Command):
action='store_true',
default=False,
help='Only output packages installed in user-site.')
cmd_opts.add_option(cmdoptions.list_path())
cmd_opts.add_option(
self.cmd_opts.add_option(cmdoptions.list_path())
self.cmd_opts.add_option(
'--pre',
action='store_true',
default=False,
@@ -73,7 +82,7 @@ class ListCommand(Command):
"pip only finds stable versions."),
)
cmd_opts.add_option(
self.cmd_opts.add_option(
'--format',
action='store',
dest='list_format',
@@ -83,7 +92,7 @@ class ListCommand(Command):
"or json",
)
cmd_opts.add_option(
self.cmd_opts.add_option(
'--not-required',
action='store_true',
dest='not_required',
@@ -91,13 +100,13 @@ class ListCommand(Command):
"installed packages.",
)
cmd_opts.add_option(
self.cmd_opts.add_option(
'--exclude-editable',
action='store_false',
dest='include_editable',
help='Exclude editable package from output.',
)
cmd_opts.add_option(
self.cmd_opts.add_option(
'--include-editable',
action='store_true',
dest='include_editable',
@@ -109,13 +118,14 @@ class ListCommand(Command):
)
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, cmd_opts)
self.parser.insert_option_group(0, self.cmd_opts)
def _build_package_finder(self, options, session):
# type: (Values, PipSession) -> PackageFinder
"""
Create a package finder appropriate to this list command.
"""
search_scope = make_search_scope(options)
link_collector = LinkCollector.create(session, options=options)
# Pass allow_yanked=False to ignore yanked versions.
selection_prefs = SelectionPreferences(
@@ -124,13 +134,12 @@ class ListCommand(Command):
)
return PackageFinder.create(
search_scope=search_scope,
link_collector=link_collector,
selection_prefs=selection_prefs,
trusted_hosts=options.trusted_hosts,
session=session,
)
def run(self, options, args):
# type: (Values, List[str]) -> int
if options.outdated and options.uptodate:
raise CommandError(
"Options --outdated and --uptodate cannot be combined.")
@@ -158,30 +167,40 @@ class ListCommand(Command):
packages = self.get_uptodate(packages, options)
self.output_package_listing(packages, options)
return SUCCESS
def get_outdated(self, packages, options):
# type: (List[Distribution], Values) -> List[Distribution]
return [
dist for dist in self.iter_packages_latest_infos(packages, options)
if dist.latest_version > dist.parsed_version
]
def get_uptodate(self, packages, options):
# type: (List[Distribution], Values) -> List[Distribution]
return [
dist for dist in self.iter_packages_latest_infos(packages, options)
if dist.latest_version == dist.parsed_version
]
def get_not_required(self, packages, options):
dep_keys = set()
# type: (List[Distribution], Values) -> List[Distribution]
dep_keys = set() # type: Set[Distribution]
for dist in packages:
dep_keys.update(requirement.key for requirement in dist.requires())
return {pkg for pkg in packages if pkg.key not in dep_keys}
# Create a set to remove duplicate packages, and cast it to a list
# to keep the return type consistent with get_outdated and
# get_uptodate
return list({pkg for pkg in packages if pkg.key not in dep_keys})
def iter_packages_latest_infos(self, packages, options):
# type: (List[Distribution], Values) -> Iterator[Distribution]
with self._build_session(options) as session:
finder = self._build_package_finder(options, session)
for dist in packages:
def latest_info(dist):
# type: (Distribution) -> Distribution
typ = 'unknown'
all_candidates = finder.find_all_candidates(dist.key)
if not options.pre:
@@ -192,9 +211,9 @@ class ListCommand(Command):
evaluator = finder.make_candidate_evaluator(
project_name=dist.project_name,
)
best_candidate = evaluator.get_best_candidate(all_candidates)
best_candidate = evaluator.sort_best_candidate(all_candidates)
if best_candidate is None:
continue
return None
remote_version = best_candidate.version
if best_candidate.link.is_wheel:
@@ -204,9 +223,14 @@ class ListCommand(Command):
# This is dirty but makes the rest of the code much cleaner
dist.latest_version = remote_version
dist.latest_filetype = typ
yield dist
return dist
for dist in map_multithread(latest_info, packages):
if dist is not None:
yield dist
def output_package_listing(self, packages, options):
# type: (List[Distribution], Values) -> None
packages = sorted(
packages,
key=lambda dist: dist.project_name.lower(),
@@ -217,14 +241,15 @@ class ListCommand(Command):
elif options.list_format == 'freeze':
for dist in packages:
if options.verbose >= 1:
logger.info("%s==%s (%s)", dist.project_name,
dist.version, dist.location)
write_output("%s==%s (%s)", dist.project_name,
dist.version, dist.location)
else:
logger.info("%s==%s", dist.project_name, dist.version)
write_output("%s==%s", dist.project_name, dist.version)
elif options.list_format == 'json':
logger.info(format_for_json(packages, options))
write_output(format_for_json(packages, options))
def output_package_listing_columns(self, data, header):
# type: (List[List[str]], List[str]) -> None
# insert the header first: we need to know the size of column names
if len(data) > 0:
data.insert(0, header)
@@ -236,28 +261,11 @@ class ListCommand(Command):
pkg_strings.insert(1, " ".join(map(lambda x: '-' * x, sizes)))
for val in pkg_strings:
logger.info(val)
def tabulate(vals):
# From pfmoore on GitHub:
# https://github.com/pypa/pip/issues/3651#issuecomment-216932564
assert len(vals) > 0
sizes = [0] * max(len(x) for x in vals)
for row in vals:
sizes = [max(s, len(str(c))) for s, c in zip_longest(sizes, row)]
result = []
for row in vals:
display = " ".join([str(c).ljust(s) if c is not None else ''
for s, c in zip_longest(sizes, row)])
result.append(display)
return result, sizes
write_output(val)
def format_for_columns(pkgs, options):
# type: (List[Distribution], Values) -> Tuple[List[List[str]], List[str]]
"""
Convert the package data into something usable
by output_package_listing_columns.
@@ -295,6 +303,7 @@ def format_for_columns(pkgs, options):
def format_for_json(packages, options):
# type: (List[Distribution], Values) -> str
data = []
for dist in packages:
info = {

View File

@@ -12,26 +12,37 @@ from pip._vendor.packaging.version import parse as parse_version
from pip._vendor.six.moves import xmlrpc_client # type: ignore
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.download import PipXmlrpcTransport
from pip._internal.exceptions import CommandError
from pip._internal.models.index import PyPI
from pip._internal.network.xmlrpc import PipXmlrpcTransport
from pip._internal.utils.compat import get_terminal_size
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import get_distribution, write_output
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List, Dict, Optional
from typing_extensions import TypedDict
TransformedHit = TypedDict(
'TransformedHit',
{'name': str, 'summary': str, 'versions': List[str]},
)
logger = logging.getLogger(__name__)
class SearchCommand(Command):
class SearchCommand(Command, SessionCommandMixin):
"""Search for PyPI packages whose name or summary contains <query>."""
name = 'search'
usage = """
%prog [options] <query>"""
summary = 'Search PyPI for packages.'
ignore_require_venv = True
def __init__(self, *args, **kw):
super(SearchCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'-i', '--index',
dest='index',
@@ -42,6 +53,7 @@ class SearchCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
# type: (Values, List[str]) -> int
if not args:
raise CommandError('Missing required argument (search query).')
query = args
@@ -58,21 +70,25 @@ class SearchCommand(Command):
return NO_MATCHES_FOUND
def search(self, query, options):
# type: (List[str], Values) -> List[Dict[str, str]]
index_url = options.index
with self._build_session(options) as session:
transport = PipXmlrpcTransport(index_url, session)
pypi = xmlrpc_client.ServerProxy(index_url, transport)
hits = pypi.search({'name': query, 'summary': query}, 'or')
return hits
session = self.get_default_session(options)
transport = PipXmlrpcTransport(index_url, session)
pypi = xmlrpc_client.ServerProxy(index_url, transport)
hits = pypi.search({'name': query, 'summary': query}, 'or')
return hits
def transform_hits(hits):
# type: (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 = OrderedDict()
packages = OrderedDict() # type: OrderedDict[str, TransformedHit]
for hit in hits:
name = hit['name']
summary = hit['summary']
@@ -95,6 +111,7 @@ def transform_hits(hits):
def print_results(hits, name_column_width=None, terminal_width=None):
# type: (List[TransformedHit], Optional[int], Optional[int]) -> None
if not hits:
return
if name_column_width is None:
@@ -112,28 +129,32 @@ def print_results(hits, name_column_width=None, terminal_width=None):
target_width = terminal_width - name_column_width - 5
if target_width > 10:
# wrap and indent summary to fit terminal
summary = textwrap.wrap(summary, target_width)
summary = ('\n' + ' ' * (name_column_width + 3)).join(summary)
summary_lines = textwrap.wrap(summary, target_width)
summary = ('\n' + ' ' * (name_column_width + 3)).join(
summary_lines)
line = '%-*s - %s' % (name_column_width,
'%s (%s)' % (name, latest), summary)
line = '{name_latest:{name_column_width}} - {summary}'.format(
name_latest='{name} ({latest})'.format(**locals()),
**locals())
try:
logger.info(line)
write_output(line)
if name in installed_packages:
dist = pkg_resources.get_distribution(name)
dist = get_distribution(name)
assert dist is not None
with indent_log():
if dist.version == latest:
logger.info('INSTALLED: %s (latest)', dist.version)
write_output('INSTALLED: %s (latest)', dist.version)
else:
logger.info('INSTALLED: %s', dist.version)
write_output('INSTALLED: %s', dist.version)
if parse_version(latest).pre:
logger.info('LATEST: %s (pre-release; install'
' with "pip install --pre")', latest)
write_output('LATEST: %s (pre-release; install'
' with "pip install --pre")', latest)
else:
logger.info('LATEST: %s', latest)
write_output('LATEST: %s', latest)
except UnicodeEncodeError:
pass
def highest_version(versions):
# type: (List[str]) -> str
return max(versions, key=parse_version)

View File

@@ -9,6 +9,12 @@ from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import ERROR, SUCCESS
from pip._internal.utils.misc import write_output
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List, Dict, Iterator
logger = logging.getLogger(__name__)
@@ -19,14 +25,13 @@ class ShowCommand(Command):
The output is in RFC-compliant mail header format.
"""
name = 'show'
usage = """
%prog [options] <package> ..."""
summary = 'Show information about installed packages.'
ignore_require_venv = True
def __init__(self, *args, **kw):
super(ShowCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'-f', '--files',
dest='files',
@@ -37,6 +42,7 @@ class ShowCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
# type: (Values, List[str]) -> int
if not args:
logger.warning('ERROR: Please provide a package name or names.')
return ERROR
@@ -50,6 +56,7 @@ class ShowCommand(Command):
def search_packages_info(query):
# type: (List[str]) -> Iterator[Dict[str, str]]
"""
Gather details from installed distributions. Print distribution name,
version, location, and installed files. Installed files requires a
@@ -61,6 +68,21 @@ def search_packages_info(query):
installed[canonicalize_name(p.project_name)] = p
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]
)
if missing:
logger.warning('Package(s) not found: %s', ', '.join(missing))
def get_requiring_packages(package_name):
# type: (str) -> List[str]
canonical_name = canonicalize_name(package_name)
return [
pkg.project_name for pkg in pkg_resources.working_set
if canonical_name in
[canonicalize_name(required.name) for required in
pkg.requires()]
]
for dist in [installed[pkg] for pkg in query_names if pkg in installed]:
package = {
@@ -68,14 +90,15 @@ def search_packages_info(query):
'version': dist.version,
'location': dist.location,
'requires': [dep.project_name for dep in dist.requires()],
'required_by': get_requiring_packages(dist.project_name)
}
file_list = None
metadata = None
metadata = ''
if isinstance(dist, pkg_resources.DistInfoDistribution):
# RECORDs should be part of .dist-info metadatas
if dist.has_metadata('RECORD'):
lines = dist.get_metadata_lines('RECORD')
paths = [l.split(',')[0] for l in lines]
paths = [line.split(',')[0] for line in lines]
paths = [os.path.join(dist.location, p) for p in paths]
file_list = [os.path.relpath(p, dist.location) for p in paths]
@@ -123,46 +146,41 @@ def search_packages_info(query):
def print_results(distributions, list_files=False, verbose=False):
# type: (Iterator[Dict[str, str]], bool, bool) -> bool
"""
Print the informations from installed distributions found.
Print the information from installed distributions found.
"""
results_printed = False
for i, dist in enumerate(distributions):
results_printed = True
if i > 0:
logger.info("---")
write_output("---")
name = dist.get('name', '')
required_by = [
pkg.project_name for pkg in pkg_resources.working_set
if name in [required.name for required in pkg.requires()]
]
logger.info("Name: %s", name)
logger.info("Version: %s", dist.get('version', ''))
logger.info("Summary: %s", dist.get('summary', ''))
logger.info("Home-page: %s", dist.get('home-page', ''))
logger.info("Author: %s", dist.get('author', ''))
logger.info("Author-email: %s", dist.get('author-email', ''))
logger.info("License: %s", dist.get('license', ''))
logger.info("Location: %s", dist.get('location', ''))
logger.info("Requires: %s", ', '.join(dist.get('requires', [])))
logger.info("Required-by: %s", ', '.join(required_by))
write_output("Name: %s", dist.get('name', ''))
write_output("Version: %s", dist.get('version', ''))
write_output("Summary: %s", dist.get('summary', ''))
write_output("Home-page: %s", dist.get('home-page', ''))
write_output("Author: %s", dist.get('author', ''))
write_output("Author-email: %s", dist.get('author-email', ''))
write_output("License: %s", dist.get('license', ''))
write_output("Location: %s", dist.get('location', ''))
write_output("Requires: %s", ', '.join(dist.get('requires', [])))
write_output("Required-by: %s", ', '.join(dist.get('required_by', [])))
if verbose:
logger.info("Metadata-Version: %s",
dist.get('metadata-version', ''))
logger.info("Installer: %s", dist.get('installer', ''))
logger.info("Classifiers:")
write_output("Metadata-Version: %s",
dist.get('metadata-version', ''))
write_output("Installer: %s", dist.get('installer', ''))
write_output("Classifiers:")
for classifier in dist.get('classifiers', []):
logger.info(" %s", classifier)
logger.info("Entry-points:")
write_output(" %s", classifier)
write_output("Entry-points:")
for entry in dist.get('entry_points', []):
logger.info(" %s", entry.strip())
write_output(" %s", entry.strip())
if list_files:
logger.info("Files:")
write_output("Files:")
for line in dist.get('files', []):
logger.info(" %s", line.strip())
write_output(" %s", line.strip())
if "files" not in dist:
logger.info("Cannot locate installed-files.txt")
write_output("Cannot locate installed-files.txt")
return results_printed

View File

@@ -3,13 +3,23 @@ from __future__ import absolute_import
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.cli.base_command import Command
from pip._internal.cli.req_command import SessionCommandMixin
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
from pip._internal.req.constructors import (
install_req_from_line,
install_req_from_parsed_requirement,
)
from pip._internal.utils.misc import protect_pip_from_modification_on_windows
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List
class UninstallCommand(Command):
class UninstallCommand(Command, SessionCommandMixin):
"""
Uninstall packages.
@@ -19,14 +29,13 @@ class UninstallCommand(Command):
leave behind no metadata to determine what files were installed.
- Script wrappers installed by ``python setup.py develop``.
"""
name = 'uninstall'
usage = """
%prog [options] <package> ...
%prog [options] -r <requirements file> ..."""
summary = 'Uninstall packages.'
def __init__(self, *args, **kw):
super(UninstallCommand, self).__init__(*args, **kw)
def add_options(self):
# type: () -> None
self.cmd_opts.add_option(
'-r', '--requirement',
dest='requirements',
@@ -45,34 +54,42 @@ class UninstallCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options, args):
with self._build_session(options) as session:
reqs_to_uninstall = {}
for name in args:
req = install_req_from_line(
name, isolated=options.isolated_mode,
# type: (Values, List[str]) -> int
session = self.get_default_session(options)
reqs_to_uninstall = {}
for name in args:
req = install_req_from_line(
name, isolated=options.isolated_mode,
)
if req.name:
reqs_to_uninstall[canonicalize_name(req.name)] = req
for filename in options.requirements:
for parsed_req in parse_requirements(
filename,
options=options,
session=session):
req = install_req_from_parsed_requirement(
parsed_req,
isolated=options.isolated_mode
)
if req.name:
reqs_to_uninstall[canonicalize_name(req.name)] = req
for filename in options.requirements:
for req in parse_requirements(
filename,
options=options,
session=session):
if req.name:
reqs_to_uninstall[canonicalize_name(req.name)] = req
if not reqs_to_uninstall:
raise InstallationError(
'You must give at least one requirement to %(name)s (see '
'"pip help %(name)s")' % dict(name=self.name)
)
protect_pip_from_modification_on_windows(
modifying_pip="pip" in reqs_to_uninstall
if not reqs_to_uninstall:
raise InstallationError(
'You must give at least one requirement to {self.name} (see '
'"pip help {self.name}")'.format(**locals())
)
for req in reqs_to_uninstall.values():
uninstall_pathset = req.uninstall(
auto_confirm=options.yes, verbose=self.verbosity > 0,
)
if uninstall_pathset:
uninstall_pathset.commit()
protect_pip_from_modification_on_windows(
modifying_pip="pip" in reqs_to_uninstall
)
for req in reqs_to_uninstall.values():
uninstall_pathset = req.uninstall(
auto_confirm=options.yes, verbose=self.verbosity > 0,
)
if uninstall_pathset:
uninstall_pathset.commit()
return SUCCESS

View File

@@ -1,19 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import logging
import os
import shutil
from pip._internal.cache import WheelCache
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import RequirementCommand
from pip._internal.exceptions import CommandError, PreviousBuildDirError
from pip._internal.legacy_resolve import Resolver
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req import RequirementSet
from pip._internal.req.req_tracker import RequirementTracker
from pip._internal.cli.req_command import RequirementCommand, with_cleanup
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.exceptions import CommandError
from pip._internal.req.req_tracker import get_requirement_tracker
from pip._internal.utils.misc import ensure_dir, normalize_path
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.wheel import WheelBuilder
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.wheel_builder import build, should_build_for_wheel_command
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List
logger = logging.getLogger(__name__)
@@ -33,7 +40,6 @@ class WheelCommand(RequirementCommand):
"""
name = 'wheel'
usage = """
%prog [options] <requirement specifier> ...
%prog [options] -r <requirements file> ...
@@ -41,14 +47,10 @@ class WheelCommand(RequirementCommand):
%prog [options] [-e] <local project path> ...
%prog [options] <archive url/path> ..."""
summary = 'Build wheels from your requirements.'
def add_options(self):
# type: () -> None
def __init__(self, *args, **kw):
super(WheelCommand, self).__init__(*args, **kw)
cmd_opts = self.cmd_opts
cmd_opts.add_option(
self.cmd_opts.add_option(
'-w', '--wheel-dir',
dest='wheel_dir',
metavar='dir',
@@ -56,29 +58,29 @@ class WheelCommand(RequirementCommand):
help=("Build wheels into <dir>, where the default is the "
"current working directory."),
)
cmd_opts.add_option(cmdoptions.no_binary())
cmd_opts.add_option(cmdoptions.only_binary())
cmd_opts.add_option(cmdoptions.prefer_binary())
cmd_opts.add_option(
self.cmd_opts.add_option(cmdoptions.no_binary())
self.cmd_opts.add_option(cmdoptions.only_binary())
self.cmd_opts.add_option(cmdoptions.prefer_binary())
self.cmd_opts.add_option(
'--build-option',
dest='build_options',
metavar='options',
action='append',
help="Extra arguments to be supplied to 'setup.py bdist_wheel'.",
)
cmd_opts.add_option(cmdoptions.no_build_isolation())
cmd_opts.add_option(cmdoptions.use_pep517())
cmd_opts.add_option(cmdoptions.no_use_pep517())
cmd_opts.add_option(cmdoptions.constraints())
cmd_opts.add_option(cmdoptions.editable())
cmd_opts.add_option(cmdoptions.requirements())
cmd_opts.add_option(cmdoptions.src())
cmd_opts.add_option(cmdoptions.ignore_requires_python())
cmd_opts.add_option(cmdoptions.no_deps())
cmd_opts.add_option(cmdoptions.build_dir())
cmd_opts.add_option(cmdoptions.progress_bar())
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
self.cmd_opts.add_option(cmdoptions.use_pep517())
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
self.cmd_opts.add_option(cmdoptions.constraints())
self.cmd_opts.add_option(cmdoptions.editable())
self.cmd_opts.add_option(cmdoptions.requirements())
self.cmd_opts.add_option(cmdoptions.src())
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
self.cmd_opts.add_option(cmdoptions.no_deps())
self.cmd_opts.add_option(cmdoptions.build_dir())
self.cmd_opts.add_option(cmdoptions.progress_bar())
cmd_opts.add_option(
self.cmd_opts.add_option(
'--global-option',
dest='global_options',
action='append',
@@ -86,7 +88,7 @@ class WheelCommand(RequirementCommand):
help="Extra global options to be supplied to the setup.py "
"call before the 'bdist_wheel' command.")
cmd_opts.add_option(
self.cmd_opts.add_option(
'--pre',
action='store_true',
default=False,
@@ -94,8 +96,7 @@ class WheelCommand(RequirementCommand):
"pip only finds stable versions."),
)
cmd_opts.add_option(cmdoptions.no_clean())
cmd_opts.add_option(cmdoptions.require_hashes())
self.cmd_opts.add_option(cmdoptions.require_hashes())
index_opts = cmdoptions.make_option_group(
cmdoptions.index_group,
@@ -103,79 +104,85 @@ class WheelCommand(RequirementCommand):
)
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, cmd_opts)
self.parser.insert_option_group(0, self.cmd_opts)
@with_cleanup
def run(self, options, args):
# type: (Values, List[str]) -> int
cmdoptions.check_install_build_global(options)
if options.build_dir:
options.build_dir = os.path.abspath(options.build_dir)
session = self.get_default_session(options)
options.src_dir = os.path.abspath(options.src_dir)
finder = self._build_package_finder(options, session)
build_delete = (not (options.no_clean or options.build_dir))
wheel_cache = WheelCache(options.cache_dir, options.format_control)
with self._build_session(options) as session:
finder = self._build_package_finder(options, session)
build_delete = (not (options.no_clean or options.build_dir))
wheel_cache = WheelCache(options.cache_dir, options.format_control)
options.wheel_dir = normalize_path(options.wheel_dir)
ensure_dir(options.wheel_dir)
with RequirementTracker() as req_tracker, TempDirectory(
options.build_dir, delete=build_delete, kind="wheel"
) as directory:
req_tracker = self.enter_context(get_requirement_tracker())
requirement_set = RequirementSet(
require_hashes=options.require_hashes,
directory = TempDirectory(
options.build_dir,
delete=build_delete,
kind="wheel",
globally_managed=True,
)
reqs = self.get_requirements(args, options, finder, session)
preparer = self.make_requirement_preparer(
temp_build_dir=directory,
options=options,
req_tracker=req_tracker,
session=session,
finder=finder,
wheel_download_dir=options.wheel_dir,
use_user_site=False,
)
resolver = self.make_resolver(
preparer=preparer,
finder=finder,
options=options,
wheel_cache=wheel_cache,
ignore_requires_python=options.ignore_requires_python,
use_pep517=options.use_pep517,
)
self.trace_basic_info(finder)
requirement_set = resolver.resolve(
reqs, check_supported_wheels=True
)
reqs_to_build = [
r for r in requirement_set.requirements.values()
if should_build_for_wheel_command(r)
]
# build wheels
build_successes, build_failures = build(
reqs_to_build,
wheel_cache=wheel_cache,
build_options=options.build_options or [],
global_options=options.global_options or [],
)
for req in build_successes:
assert req.link and req.link.is_wheel
assert req.local_file_path
# copy from cache to target directory
try:
shutil.copy(req.local_file_path, options.wheel_dir)
except OSError as e:
logger.warning(
"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"
)
try:
self.populate_requirement_set(
requirement_set, args, options, finder, session,
self.name, wheel_cache
)
preparer = RequirementPreparer(
build_dir=directory.path,
src_dir=options.src_dir,
download_dir=None,
wheel_download_dir=options.wheel_dir,
progress_bar=options.progress_bar,
build_isolation=options.build_isolation,
req_tracker=req_tracker,
)
resolver = Resolver(
preparer=preparer,
finder=finder,
session=session,
wheel_cache=wheel_cache,
use_user_site=False,
upgrade_strategy="to-satisfy-only",
force_reinstall=False,
ignore_dependencies=options.ignore_dependencies,
ignore_requires_python=options.ignore_requires_python,
ignore_installed=True,
isolated=options.isolated_mode,
use_pep517=options.use_pep517
)
resolver.resolve(requirement_set)
# build wheels
wb = WheelBuilder(
finder, preparer, wheel_cache,
build_options=options.build_options or [],
global_options=options.global_options or [],
no_clean=options.no_clean,
)
build_failures = wb.build(
requirement_set.requirements.values(), session=session,
)
if len(build_failures) != 0:
raise CommandError(
"Failed to build one or more wheels"
)
except PreviousBuildDirError:
options.no_clean = True
raise
finally:
if not options.no_clean:
requirement_set.cleanup_files()
wheel_cache.cleanup()
return SUCCESS

View File

@@ -19,7 +19,8 @@ import sys
from pip._vendor.six.moves import configparser
from pip._internal.exceptions import (
ConfigurationError, ConfigurationFileCouldNotBeLoaded,
ConfigurationError,
ConfigurationFileCouldNotBeLoaded,
)
from pip._internal.utils import appdirs
from pip._internal.utils.compat import WINDOWS, expanduser
@@ -73,6 +74,7 @@ CONFIG_BASENAME = 'pip.ini' if WINDOWS else 'pip.conf'
def get_configuration_files():
# type: () -> Dict[Kind, List[str]]
global_config_files = [
os.path.join(path, CONFIG_BASENAME)
for path in appdirs.site_config_dirs('pip')
@@ -109,7 +111,7 @@ class Configuration(object):
"""
def __init__(self, isolated, load_only=None):
# type: (bool, Kind) -> None
# type: (bool, Optional[Kind]) -> None
super(Configuration, self).__init__()
_valid_load_only = [kinds.USER, kinds.GLOBAL, kinds.SITE, None]
@@ -119,8 +121,8 @@ class Configuration(object):
", ".join(map(repr, _valid_load_only[:-1]))
)
)
self.isolated = isolated # type: bool
self.load_only = load_only # type: Optional[Kind]
self.isolated = isolated
self.load_only = load_only
# The order here determines the override order.
self._override_order = [
@@ -180,6 +182,7 @@ class Configuration(object):
"""
self._ensure_have_load_only()
assert self.load_only
fname, parser = self._get_parser_to_modify()
if parser is not None:
@@ -195,10 +198,10 @@ class Configuration(object):
def unset_value(self, key):
# type: (str) -> None
"""Unset a value in the configuration.
"""
"""Unset a value in the configuration."""
self._ensure_have_load_only()
assert self.load_only
if key not in self._config[self.load_only]:
raise ConfigurationError("No such key - {}".format(key))
@@ -206,30 +209,18 @@ class Configuration(object):
if parser is not None:
section, name = _disassemble_key(key)
# Remove the key in the parser
modified_something = False
if parser.has_section(section):
# Returns whether the option was removed or not
modified_something = parser.remove_option(section, name)
if modified_something:
# name removed from parser, section may now be empty
section_iter = iter(parser.items(section))
try:
val = next(section_iter)
except StopIteration:
val = None
if val is None:
parser.remove_section(section)
self._mark_as_modified(fname, parser)
else:
if not (parser.has_section(section)
and parser.remove_option(section, name)):
# The option was not removed.
raise ConfigurationError(
"Fatal Internal error [id=1]. Please report as a bug."
)
# The section may be empty after the option was removed.
if not parser.items(section):
parser.remove_section(section)
self._mark_as_modified(fname, parser)
del self._config[self.load_only][key]
def save(self):
@@ -275,7 +266,7 @@ class Configuration(object):
# type: () -> None
"""Loads configuration from configuration files
"""
config_files = dict(self._iter_config_files())
config_files = dict(self.iter_config_files())
if config_files[kinds.ENV][0:1] == [os.devnull]:
logger.debug(
"Skipping loading configuration files due to "
@@ -337,7 +328,7 @@ class Configuration(object):
"""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, items):
@@ -353,7 +344,7 @@ class Configuration(object):
normalized[key] = val
return normalized
def _get_environ_vars(self):
def get_environ_vars(self):
# type: () -> Iterable[Tuple[str, str]]
"""Returns a generator with all environmental vars with prefix PIP_"""
for key, val in os.environ.items():
@@ -365,7 +356,7 @@ class Configuration(object):
yield key[4:].lower(), val
# XXX: This is patched in the tests.
def _iter_config_files(self):
def iter_config_files(self):
# type: () -> Iterable[Tuple[Kind, List[str]]]
"""Yields variant and configuration files associated with it.
@@ -396,9 +387,15 @@ class Configuration(object):
# finally virtualenv configuration first trumping others
yield kinds.SITE, config_files[kinds.SITE]
def get_values_in_config(self, variant):
# type: (Kind) -> Dict[str, Any]
"""Get values present in a config file"""
return self._config[variant]
def _get_parser_to_modify(self):
# type: () -> 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.
@@ -415,3 +412,7 @@ class Configuration(object):
file_parser_tuple = (fname, parser)
if file_parser_tuple not in self._modified_parsers:
self._modified_parsers.append(file_parser_tuple)
def __repr__(self):
# type: () -> str
return "{}({!r})".format(self.__class__.__name__, self._dictionary)

View File

@@ -1,6 +1,5 @@
from pip._internal.distributions.source import SourceDistribution
from pip._internal.distributions.sdist import SourceDistribution
from pip._internal.distributions.wheel import WheelDistribution
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
@@ -12,11 +11,13 @@ def make_distribution_for_install_requirement(install_req):
# type: (InstallRequirement) -> AbstractDistribution
"""Returns a Distribution for the given InstallRequirement
"""
# If it's not an editable, is a wheel, it's a WheelDistribution
# Editable requirements will always be source distributions. They use the
# legacy logic until we create a modern standard for them.
if install_req.editable:
return SourceDistribution(install_req)
if install_req.link and install_req.is_wheel:
# If it's a wheel, it's a WheelDistribution
if install_req.is_wheel:
return WheelDistribution(install_req)
# Otherwise, a SourceDistribution

View File

@@ -2,6 +2,15 @@ import abc
from pip._vendor.six import add_metaclass
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Optional
from pip._vendor.pkg_resources import Distribution
from pip._internal.req import InstallRequirement
from pip._internal.index.package_finder import PackageFinder
@add_metaclass(abc.ABCMeta)
class AbstractDistribution(object):
@@ -21,13 +30,16 @@ class AbstractDistribution(object):
"""
def __init__(self, req):
# type: (InstallRequirement) -> None
super(AbstractDistribution, self).__init__()
self.req = req
@abc.abstractmethod
def get_pkg_resources_distribution(self):
# type: () -> Optional[Distribution]
raise NotImplementedError()
@abc.abstractmethod
def prepare_distribution_metadata(self, finder, build_isolation):
# type: (PackageFinder, bool) -> None
raise NotImplementedError()

View File

@@ -1,4 +1,11 @@
from pip._internal.distributions.base import AbstractDistribution
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Optional
from pip._vendor.pkg_resources import Distribution
from pip._internal.index.package_finder import PackageFinder
class InstalledDistribution(AbstractDistribution):
@@ -9,7 +16,9 @@ class InstalledDistribution(AbstractDistribution):
"""
def get_pkg_resources_distribution(self):
# type: () -> Optional[Distribution]
return self.req.satisfied_by
def prepare_distribution_metadata(self, finder, build_isolation):
# type: (PackageFinder, bool) -> None
pass

View File

@@ -1,80 +0,0 @@
import logging
from pip._internal.build_env import BuildEnvironment
from pip._internal.distributions.base import AbstractDistribution
from pip._internal.exceptions import InstallationError
logger = logging.getLogger(__name__)
class SourceDistribution(AbstractDistribution):
"""Represents a source distribution.
The preparation step for these needs metadata for the packages to be
generated, either using PEP 517 or using the legacy `setup.py egg_info`.
NOTE from @pradyunsg (14 June 2019)
I expect SourceDistribution class will need to be split into
`legacy_source` (setup.py based) and `source` (PEP 517 based) when we start
bringing logic for preparation out of InstallRequirement into this class.
"""
def get_pkg_resources_distribution(self):
return self.req.get_dist()
def prepare_distribution_metadata(self, finder, build_isolation):
# Prepare for building. We need to:
# 1. Load pyproject.toml (if it exists)
# 2. Set up the build environment
self.req.load_pyproject_toml()
should_isolate = self.req.use_pep517 and build_isolation
def _raise_conflicts(conflicting_with, conflicting_reqs):
raise InstallationError(
"Some build dependencies for %s conflict with %s: %s." % (
self.req, conflicting_with, ', '.join(
'%s is incompatible with %s' % (installed, wanted)
for installed, wanted in sorted(conflicting))))
if should_isolate:
# Isolate in a BuildEnvironment and install the build-time
# requirements.
self.req.build_env = BuildEnvironment()
self.req.build_env.install_requirements(
finder, self.req.pyproject_requires, 'overlay',
"Installing build dependencies"
)
conflicting, missing = self.req.build_env.check_requirements(
self.req.requirements_to_check
)
if conflicting:
_raise_conflicts("PEP 517/518 supported requirements",
conflicting)
if missing:
logger.warning(
"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)))
)
# Install any extra build dependencies that the backend requests.
# This must be done in a second pass, as the pyproject.toml
# dependencies must be installed before we can call the backend.
with self.req.build_env:
# We need to have the env active when calling the hook.
self.req.spin_message = "Getting requirements to build wheel"
reqs = self.req.pep517_backend.get_requires_for_build_wheel()
conflicting, missing = self.req.build_env.check_requirements(reqs)
if conflicting:
_raise_conflicts("the backend dependencies", conflicting)
self.req.build_env.install_requirements(
finder, missing, 'normal',
"Installing backend dependencies"
)
self.req.prepare_metadata()
self.req.assert_source_matches_version()

View File

@@ -1,6 +1,12 @@
from pip._vendor import pkg_resources
from zipfile import ZipFile
from pip._internal.distributions.base import AbstractDistribution
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.wheel import pkg_resources_distribution_for_wheel
if MYPY_CHECK_RUNNING:
from pip._vendor.pkg_resources import Distribution
from pip._internal.index.package_finder import PackageFinder
class WheelDistribution(AbstractDistribution):
@@ -10,8 +16,21 @@ class WheelDistribution(AbstractDistribution):
"""
def get_pkg_resources_distribution(self):
return list(pkg_resources.find_distributions(
self.req.source_dir))[0]
# type: () -> Distribution
"""Loads the metadata from the wheel file into memory and returns a
Distribution that uses it, not relying on the wheel file or
requirement.
"""
# Set as part of preparation during download.
assert self.req.local_file_path
# Wheels are never unnamed.
assert self.req.name
with ZipFile(self.req.local_file_path, allowZip64=True) as z:
return pkg_resources_distribution_for_wheel(
z, self.req.name, self.req.local_file_path
)
def prepare_distribution_metadata(self, finder, build_isolation):
# type: (PackageFinder, bool) -> None
pass

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
"""Exceptions used throughout package"""
from __future__ import absolute_import
from itertools import chain, groupby, repeat
@@ -8,10 +9,20 @@ from pip._vendor.six import iteritems
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Optional
from typing import Any, Optional, List, Dict, Text
from pip._vendor.pkg_resources import Distribution
from pip._vendor.requests.models import Response, Request
from pip._vendor.six import PY3
from pip._vendor.six.moves import configparser
from pip._internal.req.req_install import InstallRequirement
if PY3:
from hashlib import _Hash
else:
from hashlib import _hash as _Hash
class PipError(Exception):
"""Base pip exception"""
@@ -80,10 +91,38 @@ class CommandError(PipError):
"""Raised when there is an error in command-line arguments"""
class SubProcessError(PipError):
"""Raised when there is an error raised while executing a
command in subprocess"""
class PreviousBuildDirError(PipError):
"""Raised when there's a previous conflicting build directory"""
class NetworkConnectionError(PipError):
"""HTTP connection error"""
def __init__(self, error_msg, response=None, request=None):
# type: (Text, Response, Request) -> None
"""
Initialize NetworkConnectionError with `request` and `response`
objects.
"""
self.response = response
self.request = request
self.error_msg = error_msg
if (self.response is not None and not self.request and
hasattr(response, 'request')):
self.request = self.response.request
super(NetworkConnectionError, self).__init__(
error_msg, response, request)
def __str__(self):
# type: () -> str
return str(self.error_msg)
class InvalidWheelFilename(InstallationError):
"""Invalid wheel filename."""
@@ -92,16 +131,39 @@ class UnsupportedWheel(InstallationError):
"""Unsupported wheel."""
class MetadataInconsistent(InstallationError):
"""Built metadata contains inconsistent information.
This is raised when the metadata contains values (e.g. name and version)
that do not match the information previously obtained from sdist filename
or user-supplied ``#egg=`` value.
"""
def __init__(self, ireq, field, built):
# type: (InstallRequirement, str, Any) -> None
self.ireq = ireq
self.field = field
self.built = built
def __str__(self):
# type: () -> str
return "Requested {} has different {} in metadata: {!r}".format(
self.ireq, self.field, self.built,
)
class HashErrors(InstallationError):
"""Multiple HashError instances rolled into one for reporting"""
def __init__(self):
self.errors = []
# type: () -> None
self.errors = [] # type: List[HashError]
def append(self, error):
# type: (HashError) -> None
self.errors.append(error)
def __str__(self):
# type: () -> str
lines = []
self.errors.sort(key=lambda e: e.order)
for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__):
@@ -109,11 +171,14 @@ class HashErrors(InstallationError):
lines.extend(e.body() for e in errors_of_cls)
if lines:
return '\n'.join(lines)
return ''
def __nonzero__(self):
# type: () -> bool
return bool(self.errors)
def __bool__(self):
# type: () -> bool
return self.__nonzero__()
@@ -135,23 +200,27 @@ class HashError(InstallationError):
"""
req = None # type: Optional[InstallRequirement]
head = ''
order = None # type: Optional[int]
def body(self):
# type: () -> str
"""Return a summary of me for display under the heading.
This default implementation simply prints a description of the
triggering requirement.
:param req: The InstallRequirement that provoked this error, with
populate_link() having already been called
its link already populated by the resolver's _populate_link().
"""
return ' %s' % self._requirement_name()
return ' {}'.format(self._requirement_name())
def __str__(self):
return '%s\n%s' % (self.head, self.body())
# type: () -> str
return '{}\n{}'.format(self.head, self.body())
def _requirement_name(self):
# type: () -> str
"""Return a description of the requirement that triggered me.
This default implementation returns long description of the req, with
@@ -192,6 +261,7 @@ class HashMissing(HashError):
'has a hash.)')
def __init__(self, gotten_hash):
# type: (str) -> None
"""
:param gotten_hash: The hash of the (possibly malicious) archive we
just downloaded
@@ -199,6 +269,7 @@ class HashMissing(HashError):
self.gotten_hash = gotten_hash
def body(self):
# type: () -> str
# Dodge circular import.
from pip._internal.utils.hashes import FAVORITE_HASH
@@ -211,9 +282,9 @@ class HashMissing(HashError):
# In case someone feeds something downright stupid
# to InstallRequirement's constructor.
else getattr(self.req, 'req', None))
return ' %s --hash=%s:%s' % (package or 'unknown package',
FAVORITE_HASH,
self.gotten_hash)
return ' {} --hash={}:{}'.format(package or 'unknown package',
FAVORITE_HASH,
self.gotten_hash)
class HashUnpinned(HashError):
@@ -241,6 +312,7 @@ class HashMismatch(HashError):
'someone may have tampered with them.')
def __init__(self, allowed, gots):
# type: (Dict[str, List[str]], Dict[str, _Hash]) -> None
"""
:param allowed: A dict of algorithm names pointing to lists of allowed
hex digests
@@ -251,10 +323,12 @@ class HashMismatch(HashError):
self.gots = gots
def body(self):
return ' %s:\n%s' % (self._requirement_name(),
self._hash_comparison())
# type: () -> str
return ' {}:\n{}'.format(self._requirement_name(),
self._hash_comparison())
def _hash_comparison(self):
# type: () -> str
"""
Return a comparison of actual and expected hash values.
@@ -266,18 +340,18 @@ class HashMismatch(HashError):
"""
def hash_then_or(hash_name):
# type: (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'))
lines = []
lines = [] # type: List[str]
for hash_name, expecteds in iteritems(self.allowed):
prefix = hash_then_or(hash_name)
lines.extend((' Expected %s %s' % (next(prefix), e))
lines.extend((' Expected {} {}'.format(next(prefix), e))
for e in expecteds)
lines.append(' Got %s\n' %
self.gots[hash_name].hexdigest())
prefix = ' or'
lines.append(' Got {}\n'.format(
self.gots[hash_name].hexdigest()))
return '\n'.join(lines)
@@ -291,15 +365,17 @@ class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
"""
def __init__(self, reason="could not be loaded", fname=None, error=None):
# type: (str, Optional[str], Optional[configparser.Error]) -> None
super(ConfigurationFileCouldNotBeLoaded, self).__init__(error)
self.reason = reason
self.fname = fname
self.error = error
def __str__(self):
# type: () -> str
if self.fname is not None:
message_part = " in {}.".format(self.fname)
else:
assert self.error is not None
message_part = ".\n{}\n".format(self.error.message)
message_part = ".\n{}\n".format(self.error)
return "Configuration file {}{}".format(self.reason, message_part)

File diff suppressed because it is too large Load Diff

View File

@@ -1,457 +0,0 @@
"""Dependency Resolution
The dependency resolution in pip is performed as follows:
for top-level requirements:
a. only one spec allowed per project, regardless of conflicts or not.
otherwise a "double requirement" exception is raised
b. they override sub-dependency requirements.
for sub-dependencies
a. "first found, wins" (where the order is breadth first)
"""
import logging
import sys
from collections import defaultdict
from itertools import chain
from pip._vendor.packaging import specifiers
from pip._internal.exceptions import (
BestVersionAlreadyInstalled, DistributionNotFound, HashError, HashErrors,
UnsupportedPythonVersion,
)
from pip._internal.req.constructors import install_req_from_req_string
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import (
dist_in_usersite, ensure_dir, normalize_version_info,
)
from pip._internal.utils.packaging import (
check_requires_python, get_requires_python,
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import DefaultDict, List, Optional, Set, Tuple
from pip._vendor import pkg_resources
from pip._internal.cache import WheelCache
from pip._internal.distributions import AbstractDistribution
from pip._internal.download import PipSession
from pip._internal.index import PackageFinder
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req.req_install import InstallRequirement
from pip._internal.req.req_set import RequirementSet
logger = logging.getLogger(__name__)
def _check_dist_requires_python(
dist, # type: pkg_resources.Distribution
version_info, # type: Tuple[int, int, int]
ignore_requires_python=False, # type: bool
):
# type: (...) -> None
"""
Check whether the given Python version is compatible with a distribution's
"Requires-Python" value.
:param version_info: A 3-tuple of ints representing the Python
major-minor-micro version to check.
:param ignore_requires_python: Whether to ignore the "Requires-Python"
value if the given Python version isn't compatible.
:raises UnsupportedPythonVersion: When the given Python version isn't
compatible.
"""
requires_python = get_requires_python(dist)
try:
is_compatible = check_requires_python(
requires_python, version_info=version_info,
)
except specifiers.InvalidSpecifier as exc:
logger.warning(
"Package %r has an invalid Requires-Python: %s",
dist.project_name, exc,
)
return
if is_compatible:
return
version = '.'.join(map(str, version_info))
if ignore_requires_python:
logger.debug(
'Ignoring failed Requires-Python check for package %r: '
'%s not in %r',
dist.project_name, version, requires_python,
)
return
raise UnsupportedPythonVersion(
'Package {!r} requires a different Python: {} not in {!r}'.format(
dist.project_name, version, requires_python,
))
class Resolver(object):
"""Resolves which packages need to be installed/uninstalled to perform \
the requested operation without breaking the requirements of any package.
"""
_allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"}
def __init__(
self,
preparer, # type: RequirementPreparer
session, # type: PipSession
finder, # type: PackageFinder
wheel_cache, # type: Optional[WheelCache]
use_user_site, # type: bool
ignore_dependencies, # type: bool
ignore_installed, # type: bool
ignore_requires_python, # type: bool
force_reinstall, # type: bool
isolated, # type: bool
upgrade_strategy, # type: str
use_pep517=None, # type: Optional[bool]
py_version_info=None, # type: Optional[Tuple[int, ...]]
):
# type: (...) -> None
super(Resolver, self).__init__()
assert upgrade_strategy in self._allowed_strategies
if py_version_info is None:
py_version_info = sys.version_info[:3]
else:
py_version_info = normalize_version_info(py_version_info)
self._py_version_info = py_version_info
self.preparer = preparer
self.finder = finder
self.session = session
# NOTE: This would eventually be replaced with a cache that can give
# information about both sdist and wheels transparently.
self.wheel_cache = wheel_cache
# This is set in resolve
self.require_hashes = None # type: Optional[bool]
self.upgrade_strategy = upgrade_strategy
self.force_reinstall = force_reinstall
self.isolated = isolated
self.ignore_dependencies = ignore_dependencies
self.ignore_installed = ignore_installed
self.ignore_requires_python = ignore_requires_python
self.use_user_site = use_user_site
self.use_pep517 = use_pep517
self._discovered_dependencies = \
defaultdict(list) # type: DefaultDict[str, List]
def resolve(self, requirement_set):
# type: (RequirementSet) -> None
"""Resolve what operations need to be done
As a side-effect of this method, the packages (and their dependencies)
are downloaded, unpacked and prepared for installation. This
preparation is done by ``pip.operations.prepare``.
Once PyPI has static dependency metadata available, it would be
possible to move the preparation to become a step separated from
dependency resolution.
"""
# make the wheelhouse
if self.preparer.wheel_download_dir:
ensure_dir(self.preparer.wheel_download_dir)
# If any top-level requirement has a hash specified, enter
# hash-checking mode, which requires hashes from all.
root_reqs = (
requirement_set.unnamed_requirements +
list(requirement_set.requirements.values())
)
self.require_hashes = (
requirement_set.require_hashes or
any(req.has_hash_options for req in root_reqs)
)
# Display where finder is looking for packages
search_scope = self.finder.search_scope
locations = search_scope.get_formatted_locations()
if locations:
logger.info(locations)
# Actually prepare the files, and collect any exceptions. Most hash
# exceptions cannot be checked ahead of time, because
# req.populate_link() needs to be called before we can make decisions
# based on link type.
discovered_reqs = [] # type: List[InstallRequirement]
hash_errors = HashErrors()
for req in chain(root_reqs, discovered_reqs):
try:
discovered_reqs.extend(
self._resolve_one(requirement_set, req)
)
except HashError as exc:
exc.req = req
hash_errors.append(exc)
if hash_errors:
raise hash_errors
def _is_upgrade_allowed(self, req):
# type: (InstallRequirement) -> bool
if self.upgrade_strategy == "to-satisfy-only":
return False
elif self.upgrade_strategy == "eager":
return True
else:
assert self.upgrade_strategy == "only-if-needed"
return req.is_direct
def _set_req_to_reinstall(self, req):
# type: (InstallRequirement) -> None
"""
Set a requirement to be installed.
"""
# Don't uninstall the conflict if doing a user install and the
# conflict is not a user install.
if not self.use_user_site or dist_in_usersite(req.satisfied_by):
req.conflicts_with = req.satisfied_by
req.satisfied_by = None
# XXX: Stop passing requirement_set for options
def _check_skip_installed(self, req_to_install):
# type: (InstallRequirement) -> Optional[str]
"""Check if req_to_install should be skipped.
This will check if the req is installed, and whether we should upgrade
or reinstall it, taking into account all the relevant user options.
After calling this req_to_install will only have satisfied_by set to
None if the req_to_install is to be upgraded/reinstalled etc. Any
other value will be a dist recording the current thing installed that
satisfies the requirement.
Note that for vcs urls and the like we can't assess skipping in this
routine - we simply identify that we need to pull the thing down,
then later on it is pulled down and introspected to assess upgrade/
reinstalls etc.
:return: A text reason for why it was skipped, or None.
"""
if self.ignore_installed:
return None
req_to_install.check_if_exists(self.use_user_site)
if not req_to_install.satisfied_by:
return None
if self.force_reinstall:
self._set_req_to_reinstall(req_to_install)
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'
# Check for the possibility of an upgrade. For link-based
# requirements we have to pull the tree down and inspect to assess
# the version #, so it's handled way down.
if not req_to_install.link:
try:
self.finder.find_requirement(req_to_install, upgrade=True)
except BestVersionAlreadyInstalled:
# Then the best version is installed.
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.
# Why don't we just raise here?
pass
self._set_req_to_reinstall(req_to_install)
return None
def _get_abstract_dist_for(self, req):
# type: (InstallRequirement) -> AbstractDistribution
"""Takes a InstallRequirement and returns a single AbstractDist \
representing a prepared variant of the same.
"""
assert self.require_hashes is not None, (
"require_hashes should have been set in Resolver.resolve()"
)
if req.editable:
return self.preparer.prepare_editable_requirement(
req, self.require_hashes, self.use_user_site, self.finder,
)
# satisfied_by is only evaluated by calling _check_skip_installed,
# so it must be None here.
assert req.satisfied_by is None
skip_reason = self._check_skip_installed(req)
if req.satisfied_by:
return self.preparer.prepare_installed_requirement(
req, self.require_hashes, skip_reason
)
upgrade_allowed = self._is_upgrade_allowed(req)
abstract_dist = self.preparer.prepare_linked_requirement(
req, self.session, self.finder, upgrade_allowed,
self.require_hashes
)
# NOTE
# The following portion is for determining if a certain package is
# going to be re-installed/upgraded or not and reporting to the user.
# This should probably get cleaned up in a future refactor.
# req.req is only avail after unpack for URL
# pkgs repeat check_if_exists to uninstall-on-upgrade
# (#14)
if not self.ignore_installed:
req.check_if_exists(self.use_user_site)
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'
)
if should_modify:
self._set_req_to_reinstall(req)
else:
logger.info(
'Requirement already satisfied (use --upgrade to upgrade):'
' %s', req,
)
return abstract_dist
def _resolve_one(
self,
requirement_set, # type: RequirementSet
req_to_install # type: InstallRequirement
):
# type: (...) -> List[InstallRequirement]
"""Prepare a single requirements file.
:return: A list of additional InstallRequirements to also install.
"""
# Tell user what we are doing for this requirement:
# obtain (editable), skipping, processing (local url), collecting
# (remote url or package name)
if req_to_install.constraint or req_to_install.prepared:
return []
req_to_install.prepared = True
# register tmp src for cleanup in case something goes wrong
requirement_set.reqs_to_cleanup.append(req_to_install)
abstract_dist = self._get_abstract_dist_for(req_to_install)
# Parse and return dependencies
dist = abstract_dist.get_pkg_resources_distribution()
# This will raise UnsupportedPythonVersion if the given Python
# version isn't compatible with the distribution's Requires-Python.
_check_dist_requires_python(
dist, version_info=self._py_version_info,
ignore_requires_python=self.ignore_requires_python,
)
more_reqs = [] # type: List[InstallRequirement]
def add_req(subreq, extras_requested):
sub_install_req = install_req_from_req_string(
str(subreq),
req_to_install,
isolated=self.isolated,
wheel_cache=self.wheel_cache,
use_pep517=self.use_pep517
)
parent_req_name = req_to_install.name
to_scan_again, add_to_parent = requirement_set.add_requirement(
sub_install_req,
parent_req_name=parent_req_name,
extras_requested=extras_requested,
)
if parent_req_name and add_to_parent:
self._discovered_dependencies[parent_req_name].append(
add_to_parent
)
more_reqs.extend(to_scan_again)
with indent_log():
# We add req_to_install before its dependencies, so that we
# can refer to it when adding dependencies.
if not requirement_set.has_requirement(req_to_install.name):
# 'unnamed' requirements will get added here
req_to_install.is_direct = True
requirement_set.add_requirement(
req_to_install, parent_req_name=None,
)
if not self.ignore_dependencies:
if req_to_install.extras:
logger.debug(
"Installing extra requirements: %r",
','.join(req_to_install.extras),
)
missing_requested = sorted(
set(req_to_install.extras) - set(dist.extras)
)
for missing in missing_requested:
logger.warning(
'%s does not provide the extra \'%s\'',
dist, missing
)
available_requested = sorted(
set(dist.extras) & set(req_to_install.extras)
)
for subreq in dist.requires(available_requested):
add_req(subreq, extras_requested=available_requested)
if not req_to_install.editable and not req_to_install.satisfied_by:
# XXX: --no-install leads this to report 'Successfully
# downloaded' for only non-editable reqs, even though we took
# action on them.
requirement_set.successfully_downloaded.append(req_to_install)
return more_reqs
def get_installation_order(self, req_set):
# type: (RequirementSet) -> List[InstallRequirement]
"""Create the installation order.
The installation order is topological - requirements are installed
before the requiring thing. We break cycles at an arbitrary point,
and make no other guarantees.
"""
# The current implementation, which we may change at any point
# installs the user specified things in the order given, except when
# dependencies must come earlier to achieve topological order.
order = []
ordered_reqs = set() # type: Set[InstallRequirement]
def schedule(req):
if req.satisfied_by or req in ordered_reqs:
return
if req.constraint:
return
ordered_reqs.add(req)
for dep in self._discovered_dependencies[req.name]:
schedule(dep)
order.append(req)
for install_req in req_set.requirements.values():
schedule(install_req)
return order

View File

@@ -1,4 +1,8 @@
"""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 absolute_import
import os
@@ -9,21 +13,35 @@ import sys
import sysconfig
from distutils import sysconfig as distutils_sysconfig
from distutils.command.install import SCHEME_KEYS # type: ignore
from distutils.command.install import install as distutils_install_command
from pip._internal.models.scheme import Scheme
from pip._internal.utils import appdirs
from pip._internal.utils.compat import WINDOWS
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.typing import MYPY_CHECK_RUNNING, cast
from pip._internal.utils.virtualenv import running_under_virtualenv
if MYPY_CHECK_RUNNING:
from typing import Any, Union, Dict, List, Optional
from typing import Dict, List, Optional, Union
from distutils.cmd import Command as DistutilsCommand
# Application Directories
USER_CACHE_DIR = appdirs.user_cache_dir("pip")
def get_major_minor_version():
# type: () -> 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)
def get_src_prefix():
# type: () -> str
if running_under_virtualenv():
src_prefix = os.path.join(sys.prefix, 'src')
else:
@@ -74,29 +92,25 @@ else:
bin_py = '/usr/local/bin'
def distutils_scheme(dist_name, user=False, home=None, root=None,
isolated=False, prefix=None):
# type:(str, bool, str, str, bool, str) -> dict
def distutils_scheme(
dist_name, user=False, home=None, root=None, isolated=False, prefix=None
):
# type:(str, bool, str, str, bool, str) -> Dict[str, str]
"""
Return a distutils install scheme
"""
from distutils.dist import Distribution
scheme = {}
if isolated:
extra_dist_args = {"script_args": ["--no-user-cfg"]}
else:
extra_dist_args = {}
dist_args = {'name': dist_name} # type: Dict[str, Union[str, List[str]]]
dist_args.update(extra_dist_args)
if isolated:
dist_args["script_args"] = ["--no-user-cfg"]
d = Distribution(dist_args)
# Ignoring, typeshed issue reported python/typeshed/issues/2567
d.parse_config_files()
# NOTE: Ignoring type since mypy can't find attributes on 'Command'
i = d.get_command_obj('install', create=True) # type: Any
assert i is not None
obj = None # type: Optional[DistutilsCommand]
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.
@@ -109,6 +123,8 @@ def distutils_scheme(dist_name, user=False, home=None, root=None,
i.home = home or i.home
i.root = root or i.root
i.finalize_options()
scheme = {}
for key in SCHEME_KEYS:
scheme[key] = getattr(i, 'install_' + key)
@@ -117,17 +133,15 @@ def distutils_scheme(dist_name, user=False, home=None, root=None,
# 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
# Ignoring, typeshed issue reported python/typeshed/issues/2567
if 'install_lib' in d.get_option_dict('install'): # type: ignore
if 'install_lib' in d.get_option_dict('install'):
scheme.update(dict(purelib=i.install_lib, platlib=i.install_lib))
if running_under_virtualenv():
scheme['headers'] = os.path.join(
sys.prefix,
i.prefix,
'include',
'site',
'python' + sys.version[:3],
'python{}'.format(get_major_minor_version()),
dist_name,
)
@@ -140,3 +154,41 @@ def distutils_scheme(dist_name, user=False, home=None, root=None,
)
return scheme
def get_scheme(
dist_name, # type: str
user=False, # type: bool
home=None, # type: Optional[str]
root=None, # type: Optional[str]
isolated=False, # type: bool
prefix=None, # type: Optional[str]
):
# type: (...) -> Scheme
"""
Get the "scheme" corresponding to the input parameters. The distutils
documentation provides the context for the available schemes:
https://docs.python.org/3/install/index.html#alternate-installation
:param dist_name: the name of the package to retrieve the scheme for, used
in the headers scheme path
:param user: indicates to use the "user" scheme
:param home: indicates to use the "home" scheme and provides the base
directory for the same
:param root: root under which other directories are re-based
:param isolated: equivalent to --no-user-cfg, i.e. do not consider
~/.pydistutils.cfg (posix) or ~/pydistutils.cfg (non-posix) for
scheme paths
:param prefix: indicates to use the "prefix" scheme and provides the
base directory for the same
"""
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"],
)

View File

@@ -6,31 +6,33 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from pip._vendor.packaging.version import _BaseVersion
from pip._internal.models.link import Link
from typing import Any
class InstallationCandidate(KeyBasedCompareMixin):
"""Represents a potential "candidate" for installation.
"""
def __init__(self, project, version, link):
# type: (Any, str, Link) -> None
self.project = project
__slots__ = ["name", "version", "link"]
def __init__(self, name, version, link):
# type: (str, str, Link) -> None
self.name = name
self.version = parse_version(version) # type: _BaseVersion
self.link = link
super(InstallationCandidate, self).__init__(
key=(self.project, self.version, self.link),
key=(self.name, self.version, self.link),
defining_class=InstallationCandidate
)
def __repr__(self):
# type: () -> str
return "<InstallationCandidate({!r}, {!r}, {!r})>".format(
self.project, self.version, self.link,
self.name, self.version, self.link,
)
def __str__(self):
# type: () -> str
return '{!r} candidate (version {} at {})'.format(
self.project, self.version, self.link,
self.name, self.version, self.link,
)

View File

@@ -1,5 +1,6 @@
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.exceptions import CommandError
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
@@ -10,8 +11,10 @@ class FormatControl(object):
"""Helper for managing formats from which a package can be installed.
"""
__slots__ = ["no_binary", "only_binary"]
def __init__(self, no_binary=None, only_binary=None):
# type: (Optional[Set], Optional[Set]) -> None
# type: (Optional[Set[str]], Optional[Set[str]]) -> None
if no_binary is None:
no_binary = set()
if only_binary is None:
@@ -21,12 +24,24 @@ class FormatControl(object):
self.only_binary = only_binary
def __eq__(self, other):
return self.__dict__ == other.__dict__
# type: (object) -> bool
if not isinstance(other, self.__class__):
return NotImplemented
if self.__slots__ != other.__slots__:
return False
return all(
getattr(self, k) == getattr(other, k)
for k in self.__slots__
)
def __ne__(self, other):
# type: (object) -> bool
return not self.__eq__(other)
def __repr__(self):
# type: () -> str
return "{}({}, {})".format(
self.__class__.__name__,
self.no_binary,
@@ -35,7 +50,11 @@ class FormatControl(object):
@staticmethod
def handle_mutual_excludes(value, target, other):
# type: (str, Optional[Set], Optional[Set]) -> None
# type: (str, Set[str], Set[str]) -> None
if value.startswith('-'):
raise CommandError(
"--no-binary / --only-binary option requires 1 argument."
)
new = value.split(',')
while ':all:' in new:
other.clear()
@@ -54,7 +73,7 @@ class FormatControl(object):
target.add(name)
def get_allowed_formats(self, canonical_name):
# type: (str) -> FrozenSet
# type: (str) -> FrozenSet[str]
result = {"binary", "source"}
if canonical_name in self.only_binary:
result.discard('source')

View File

@@ -5,6 +5,9 @@ class PackageIndex(object):
"""Represents a Package Index and provides easier access to endpoints
"""
__slots__ = ['url', 'netloc', 'simple_url', 'pypi_url',
'file_storage_domain']
def __init__(self, url, file_storage_domain):
# type: (str, str) -> None
super(PackageIndex, self).__init__()

View File

@@ -1,18 +1,22 @@
import os
import posixpath
import re
from pip._vendor.six.moves.urllib import parse as urllib_parse
from pip._internal.utils.filetypes import WHEEL_EXTENSION
from pip._internal.utils.misc import (
WHEEL_EXTENSION, path_to_url, redact_password_from_url,
split_auth_from_netloc, splitext,
redact_auth_from_url,
split_auth_from_netloc,
splitext,
)
from pip._internal.utils.models import KeyBasedCompareMixin
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.urls import path_to_url, url_to_path
if MYPY_CHECK_RUNNING:
from typing import Optional, Text, Tuple, Union
from pip._internal.index import HTMLPage
from pip._internal.index.collector import HTMLPage
from pip._internal.utils.hashes import Hashes
@@ -20,12 +24,22 @@ 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",
]
def __init__(
self,
url, # type: str
comes_from=None, # type: Optional[Union[str, HTMLPage]]
requires_python=None, # type: Optional[str]
yanked_reason=None, # type: Optional[Text]
cache_link_parsing=True, # type: bool
):
# type: (...) -> None
"""
@@ -42,6 +56,11 @@ class Link(KeyBasedCompareMixin):
a simple repository HTML link. If the file has been yanked but
no reason was provided, this should be the empty string. See
PEP 592 for more information and the specification.
:param cache_link_parsing: A flag that is used elsewhere to determine
whether resources retrieved from this link
should be cached. PyPI index urls should
generally have this set to False, for
example.
"""
# url can be a UNC windows share
@@ -59,19 +78,23 @@ class Link(KeyBasedCompareMixin):
super(Link, self).__init__(key=url, defining_class=Link)
self.cache_link_parsing = cache_link_parsing
def __str__(self):
# type: () -> str
if self.requires_python:
rp = ' (requires-python:%s)' % self.requires_python
rp = ' (requires-python:{})'.format(self.requires_python)
else:
rp = ''
if self.comes_from:
return '%s (from %s)%s' % (redact_password_from_url(self._url),
self.comes_from, rp)
return '{} (from {}){}'.format(
redact_auth_from_url(self._url), self.comes_from, rp)
else:
return redact_password_from_url(str(self._url))
return redact_auth_from_url(str(self._url))
def __repr__(self):
return '<Link %s>' % self
# type: () -> str
return '<Link {}>'.format(self)
@property
def url(self):
@@ -90,9 +113,15 @@ class Link(KeyBasedCompareMixin):
return netloc
name = urllib_parse.unquote(name)
assert name, ('URL %r produced no filename' % self._url)
assert name, (
'URL {self._url!r} produced no filename'.format(**locals()))
return name
@property
def file_path(self):
# type: () -> str
return url_to_path(self.url)
@property
def scheme(self):
# type: () -> str
@@ -168,27 +197,29 @@ class Link(KeyBasedCompareMixin):
@property
def show_url(self):
# type: () -> Optional[str]
# type: () -> str
return posixpath.basename(self._url.split('#', 1)[0].split('?', 1)[0])
@property
def is_file(self):
# type: () -> bool
return self.scheme == 'file'
def is_existing_dir(self):
# type: () -> bool
return self.is_file and os.path.isdir(self.file_path)
@property
def is_wheel(self):
# type: () -> bool
return self.ext == WHEEL_EXTENSION
@property
def is_artifact(self):
def is_vcs(self):
# type: () -> bool
"""
Determines if this points to an actual artifact (e.g. a tarball) or if
it points to an "abstract" thing like a path or a VCS location.
"""
from pip._internal.vcs import vcs
if self.scheme in vcs.all_schemes:
return False
return True
return self.scheme in vcs.all_schemes
@property
def is_yanked(self):
@@ -197,6 +228,7 @@ class Link(KeyBasedCompareMixin):
@property
def has_hash(self):
# type: () -> bool
return self.hash_name is not None
def is_hash_allowed(self, hashes):

View File

@@ -7,8 +7,8 @@ from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.six.moves.urllib import parse as urllib_parse
from pip._internal.models.index import PyPI
from pip._internal.utils.compat import HAS_TLS
from pip._internal.utils.misc import normalize_path, redact_password_from_url
from pip._internal.utils.compat import has_tls
from pip._internal.utils.misc import normalize_path, redact_auth_from_url
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
@@ -24,6 +24,8 @@ class SearchScope(object):
Encapsulates the locations that pip is configured to search.
"""
__slots__ = ["find_links", "index_urls"]
@classmethod
def create(
cls,
@@ -49,7 +51,7 @@ class SearchScope(object):
# If we don't have TLS enabled, then WARN if anyplace we're looking
# relies on TLS.
if not HAS_TLS:
if not has_tls():
for link in itertools.chain(index_urls, built_find_links):
parsed = urllib_parse.urlparse(link)
if parsed.scheme == 'https':
@@ -77,15 +79,34 @@ class SearchScope(object):
def get_formatted_locations(self):
# type: () -> str
lines = []
redacted_index_urls = []
if self.index_urls and self.index_urls != [PyPI.simple_url]:
lines.append(
'Looking in indexes: {}'.format(', '.join(
redact_password_from_url(url) for url in self.index_urls))
)
for url in self.index_urls:
redacted_index_url = redact_auth_from_url(url)
# Parse the URL
purl = urllib_parse.urlsplit(redacted_index_url)
# URL is generally invalid if scheme and netloc is missing
# there are issues with Python and URL parsing, so this test
# is a bit crude. See bpo-20271, bpo-23505. Python doesn't
# always parse invalid URLs correctly - it should raise
# exceptions for malformed URLs
if not purl.scheme and not purl.netloc:
logger.warning(
'The index url "%s" seems invalid, '
'please provide a scheme.', redacted_index_url)
redacted_index_urls.append(redacted_index_url)
lines.append('Looking in indexes: {}'.format(
', '.join(redacted_index_urls)))
if self.find_links:
lines.append(
'Looking in links: {}'.format(', '.join(
redact_password_from_url(url) for url in self.find_links))
redact_auth_from_url(url) for url in self.find_links))
)
return '\n'.join(lines)
@@ -98,6 +119,7 @@ class SearchScope(object):
"""
def mkurl_pypi_url(url):
# type: (str) -> str
loc = posixpath.join(
url,
urllib_parse.quote(canonicalize_name(project_name)))

View File

@@ -6,12 +6,14 @@ if MYPY_CHECK_RUNNING:
class SelectionPreferences(object):
"""
Encapsulates the candidate selection preferences for downloading
and installing files.
"""
__slots__ = ['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
# site considers whether yanked releases are allowed. This also causes
# that decision to be made explicit in the calling code, which helps

View File

@@ -1,12 +1,16 @@
import sys
from pip._internal.pep425tags import get_supported, version_info_to_nodot
from pip._internal.utils.compatibility_tags import (
get_supported,
version_info_to_nodot,
)
from pip._internal.utils.misc import normalize_version_info
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import List, Optional, Tuple
from pip._internal.pep425tags import Pep425Tag
from pip._vendor.packaging.tags import Tag
class TargetPython(object):
@@ -16,6 +20,16 @@ class TargetPython(object):
for a package install, download, etc.
"""
__slots__ = [
"_given_py_version_info",
"abi",
"implementation",
"platform",
"py_version",
"py_version_info",
"_valid_tags",
]
def __init__(
self,
platform=None, # type: Optional[str]
@@ -33,10 +47,10 @@ class TargetPython(object):
:param py_version_info: An optional tuple of ints representing the
Python version information to use (e.g. `sys.version_info[:3]`).
This can have length 1, 2, or 3 when provided.
:param abi: A string or None. This is passed to pep425tags.py's
:param abi: A string or None. This is passed to compatibility_tags.py's
get_supported() function as is.
:param implementation: A string or None. This is passed to
pep425tags.py's get_supported() function as is.
compatibility_tags.py's get_supported() function as is.
"""
# Store the given py_version_info for when we call get_supported().
self._given_py_version_info = py_version_info
@@ -55,7 +69,7 @@ class TargetPython(object):
self.py_version_info = py_version_info
# This is used to cache the return value of get_tags().
self._valid_tags = None # type: Optional[List[Pep425Tag]]
self._valid_tags = None # type: Optional[List[Tag]]
def format_given(self):
# type: () -> str
@@ -80,7 +94,7 @@ class TargetPython(object):
)
def get_tags(self):
# type: () -> List[Pep425Tag]
# type: () -> List[Tag]
"""
Return the supported PEP 425 tags to check wheel candidates against.
@@ -91,12 +105,12 @@ class TargetPython(object):
# versions=None uses special default logic.
py_version_info = self._given_py_version_info
if py_version_info is None:
versions = None
version = None
else:
versions = [version_info_to_nodot(py_version_info)]
version = version_info_to_nodot(py_version_info)
tags = get_supported(
versions=versions,
version=version,
platform=self.platform,
abi=self.abi,
impl=self.implementation,

View File

@@ -29,6 +29,7 @@ if MYPY_CHECK_RUNNING:
MissingDict = Dict[str, List[Missing]]
ConflictingDict = Dict[str, List[Conflicting]]
CheckResult = Tuple[MissingDict, ConflictingDict]
ConflictDetails = Tuple[PackageSet, CheckResult]
PackageDetails = namedtuple('PackageDetails', ['version', 'requires'])
@@ -47,9 +48,9 @@ def create_package_set_from_installed(**kwargs):
name = canonicalize_name(dist.project_name)
try:
package_set[name] = PackageDetails(dist.version, dist.requires())
except RequirementParseError as e:
# Don't crash on broken metadata
logging.warning("Error parsing requirements for %s: %s", name, e)
except (OSError, RequirementParseError) as e:
# Don't crash on unreadable or broken metadata
logger.warning("Error parsing requirements for %s: %s", name, e)
problems = True
return package_set, problems
@@ -61,19 +62,16 @@ def check_package_set(package_set, should_ignore=None):
If should_ignore is passed, it should be a callable that takes a
package name and returns a boolean.
"""
if should_ignore is None:
def should_ignore(name):
return False
missing = dict()
conflicting = dict()
missing = {}
conflicting = {}
for package_name in package_set:
# Info about dependencies of package_name
missing_deps = set() # type: Set[Missing]
conflicting_deps = set() # type: Set[Conflicting]
if should_ignore(package_name):
if should_ignore and should_ignore(package_name):
continue
for req in package_set[package_name].requires:
@@ -102,7 +100,7 @@ def check_package_set(package_set, should_ignore=None):
def check_install_conflicts(to_install):
# type: (List[InstallRequirement]) -> Tuple[PackageSet, CheckResult]
# type: (List[InstallRequirement]) -> ConflictDetails
"""For checking if the dependency graph would be consistent after \
installing given requirements
"""
@@ -135,6 +133,7 @@ def _simulate_installation_of(to_install, package_set):
abstract_dist = make_distribution_for_install_requirement(inst_req)
dist = abstract_dist.get_pkg_resources_distribution()
assert dist is not None
name = canonicalize_name(dist.key)
package_set[name] = PackageDetails(dist.version, dist.requires())

View File

@@ -3,7 +3,6 @@ from __future__ import absolute_import
import collections
import logging
import os
import re
from pip._vendor import six
from pip._vendor.packaging.utils import canonicalize_name
@@ -11,11 +10,17 @@ from pip._vendor.pkg_resources import RequirementParseError
from pip._internal.exceptions import BadCommand, InstallationError
from pip._internal.req.constructors import (
install_req_from_editable, install_req_from_line,
install_req_from_editable,
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,
dist_get_direct_url,
)
from pip._internal.utils.misc import (
dist_is_editable, get_installed_distributions,
dist_is_editable,
get_installed_distributions,
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@@ -37,10 +42,9 @@ logger = logging.getLogger(__name__)
def freeze(
requirement=None, # type: Optional[List[str]]
find_links=None, # type: Optional[List[str]]
local_only=None, # type: Optional[bool]
user_only=None, # type: Optional[bool]
local_only=False, # type: bool
user_only=False, # type: bool
paths=None, # type: Optional[List[str]]
skip_regex=None, # type: Optional[str]
isolated=False, # type: bool
wheel_cache=None, # type: Optional[WheelCache]
exclude_editable=False, # type: bool
@@ -48,18 +52,17 @@ def freeze(
):
# type: (...) -> Iterator[str]
find_links = find_links or []
skip_match = None
if skip_regex:
skip_match = re.compile(skip_regex).search
for link in find_links:
yield '-f %s' % link
yield '-f {}'.format(link)
installations = {} # type: Dict[str, FrozenRequirement]
for dist in get_installed_distributions(local_only=local_only,
skip=(),
user_only=user_only,
paths=paths):
for dist in get_installed_distributions(
local_only=local_only,
skip=(),
user_only=user_only,
paths=paths
):
try:
req = FrozenRequirement.from_dist(dist)
except RequirementParseError as exc:
@@ -74,7 +77,7 @@ def freeze(
continue
if exclude_editable and req.editable:
continue
installations[req.name] = req
installations[req.canonical_name] = req
if requirement:
# the options that don't get turned into an InstallRequirement
@@ -90,16 +93,15 @@ def freeze(
for line in req_file:
if (not line.strip() or
line.strip().startswith('#') or
(skip_match and skip_match(line)) or
line.startswith((
'-r', '--requirement',
'-Z', '--always-unzip',
'-f', '--find-links',
'-i', '--index-url',
'--pre',
'--trusted-host',
'--process-dependency-links',
'--extra-index-url'))):
'--extra-index-url',
'--use-feature'))):
line = line.rstrip()
if line not in emitted_options:
emitted_options.add(line)
@@ -114,13 +116,11 @@ def freeze(
line_req = install_req_from_editable(
line,
isolated=isolated,
wheel_cache=wheel_cache,
)
else:
line_req = install_req_from_line(
COMMENT_RE.sub('', line).strip(),
isolated=isolated,
wheel_cache=wheel_cache,
)
if not line_req.name:
@@ -133,22 +133,27 @@ def freeze(
" (add #egg=PackageName to the URL to avoid"
" this warning)"
)
elif line_req.name not in installations:
# either it's not installed, or it is installed
# 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",
req_file_path,
COMMENT_RE.sub('', line).strip(), line_req.name
)
else:
req_files[line_req.name].append(req_file_path)
else:
yield str(installations[line_req.name]).rstrip()
del installations[line_req.name]
req_files[line_req.name].append(req_file_path)
line_req_canonical_name = canonicalize_name(
line_req.name)
if line_req_canonical_name not in installations:
# either it's not installed, or it is installed
# 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",
req_file_path,
COMMENT_RE.sub('', line).strip(),
line_req.name
)
else:
req_files[line_req.name].append(req_file_path)
else:
yield str(installations[
line_req_canonical_name]).rstrip()
del installations[line_req_canonical_name]
req_files[line_req.name].append(req_file_path)
# Warn about requirements that were included multiple times (in a
# single requirements file or in different requirements files).
@@ -163,7 +168,7 @@ def freeze(
)
for installation in sorted(
installations.values(), key=lambda x: x.name.lower()):
if canonicalize_name(installation.name) not in skip:
if installation.canonical_name not in skip:
yield str(installation).rstrip()
@@ -233,6 +238,7 @@ class FrozenRequirement(object):
def __init__(self, name, req, editable, comments=()):
# type: (str, Union[str, Requirement], bool, Iterable[str]) -> None
self.name = name
self.canonical_name = canonicalize_name(name)
self.req = req
self.editable = editable
self.comments = comments
@@ -240,14 +246,27 @@ class FrozenRequirement(object):
@classmethod
def from_dist(cls, dist):
# type: (Distribution) -> FrozenRequirement
# TODO `get_requirement_info` is taking care of editable requirements.
# TODO This should be refactored when we will add detection of
# editable that provide .dist-info metadata.
req, editable, comments = get_requirement_info(dist)
if req is None and not editable:
# if PEP 610 metadata is present, attempt to use it
direct_url = dist_get_direct_url(dist)
if direct_url:
req = direct_url_as_pep440_direct_reference(
direct_url, dist.project_name
)
comments = []
if req is None:
# name==version requirement
req = dist.as_requirement()
return cls(dist.project_name, req, editable, comments=comments)
def __str__(self):
# type: () -> str
req = self.req
if self.editable:
req = '-e %s' % req
req = '-e {}'.format(req)
return '\n'.join(list(self.comments) + [str(req)]) + '\n'

View File

@@ -1,40 +1,319 @@
"""Prepares a distribution for installation
"""
import logging
import os
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from pip._vendor import requests
import logging
import mimetypes
import os
import shutil
from pip._vendor.six import PY2
from pip._internal.distributions import (
make_distribution_for_install_requirement,
)
from pip._internal.distributions.installed import InstalledDistribution
from pip._internal.download import (
is_dir_url, is_file_url, is_vcs_url, unpack_url, url_to_path,
)
from pip._internal.exceptions import (
DirectoryUrlHashUnsupported, HashUnpinned, InstallationError,
PreviousBuildDirError, VcsHashUnsupported,
DirectoryUrlHashUnsupported,
HashMismatch,
HashUnpinned,
InstallationError,
NetworkConnectionError,
PreviousBuildDirError,
VcsHashUnsupported,
)
from pip._internal.utils.compat import expanduser
from pip._internal.utils.filesystem import copy2_fixed
from pip._internal.utils.hashes import MissingHashes
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import display_path, normalize_path
from pip._internal.utils.misc import (
display_path,
hide_url,
path_to_display,
rmtree,
)
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.unpacking import unpack_file
from pip._internal.vcs import vcs
if MYPY_CHECK_RUNNING:
from typing import Optional
from typing import (
Callable, List, Optional, Tuple,
)
from mypy_extensions import TypedDict
from pip._internal.distributions import AbstractDistribution
from pip._internal.download import PipSession
from pip._internal.index import PackageFinder
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.link import Link
from pip._internal.network.download import Downloader
from pip._internal.req.req_install import InstallRequirement
from pip._internal.req.req_tracker import RequirementTracker
from pip._internal.utils.hashes import Hashes
if PY2:
CopytreeKwargs = TypedDict(
'CopytreeKwargs',
{
'ignore': Callable[[str, List[str]], List[str]],
'symlinks': bool,
},
total=False,
)
else:
CopytreeKwargs = TypedDict(
'CopytreeKwargs',
{
'copy_function': Callable[[str, str], None],
'ignore': Callable[[str, List[str]], List[str]],
'ignore_dangling_symlinks': bool,
'symlinks': bool,
},
total=False,
)
logger = logging.getLogger(__name__)
def _get_prepared_distribution(
req, # type: InstallRequirement
req_tracker, # type: RequirementTracker
finder, # type: PackageFinder
build_isolation # type: bool
):
# type: (...) -> AbstractDistribution
"""Prepare a distribution for installation.
"""
abstract_dist = make_distribution_for_install_requirement(req)
with req_tracker.track(req):
abstract_dist.prepare_distribution_metadata(finder, build_isolation)
return abstract_dist
def unpack_vcs_link(link, location):
# type: (Link, str) -> None
vcs_backend = vcs.get_backend_for_scheme(link.scheme)
assert vcs_backend is not None
vcs_backend.unpack(location, url=hide_url(link.url))
class File(object):
def __init__(self, path, content_type):
# type: (str, str) -> None
self.path = path
self.content_type = content_type
def get_http_url(
link, # type: Link
downloader, # type: Downloader
download_dir=None, # type: Optional[str]
hashes=None, # type: Optional[Hashes]
):
# type: (...) -> File
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:
already_downloaded_path = _check_download_dir(
link, download_dir, hashes
)
if already_downloaded_path:
from_path = already_downloaded_path
content_type = mimetypes.guess_type(from_path)[0]
else:
# let's download to a tmp dir
from_path, content_type = _download_http_url(
link, downloader, temp_dir.path, hashes
)
return File(from_path, content_type)
def _copy2_ignoring_special_files(src, dest):
# type: (str, str) -> None
"""Copying special files is not supported, but as a convenience to users
we skip errors copying them. This supports tools that may create e.g.
socket files in the project source directory.
"""
try:
copy2_fixed(src, dest)
except shutil.SpecialFileError as e:
# SpecialFileError may be raised due to either the source or
# destination. If the destination was the cause then we would actually
# care, but since the destination directory is deleted prior to
# copy we ignore all of them assuming it is caused by the source.
logger.warning(
"Ignoring special file error '%s' encountered copying %s to %s.",
str(e),
path_to_display(src),
path_to_display(dest),
)
def _copy_source_tree(source, target):
# type: (str, str) -> None
target_abspath = os.path.abspath(target)
target_basename = os.path.basename(target_abspath)
target_dirname = os.path.dirname(target_abspath)
def ignore(d, names):
# type: (str, List[str]) -> List[str]
skipped = [] # type: 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']
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}/...
# and we copy PWD to TMPDIR.
skipped += [target_basename]
return skipped
kwargs = dict(ignore=ignore, symlinks=True) # type: CopytreeKwargs
if not PY2:
# Python 2 does not support copy_function, so we only ignore
# errors on special file copy in Python 3.
kwargs['copy_function'] = _copy2_ignoring_special_files
shutil.copytree(source, target, **kwargs)
def get_file_url(
link, # type: Link
download_dir=None, # type: Optional[str]
hashes=None # type: Optional[Hashes]
):
# type: (...) -> File
"""Get file and optionally check its hash.
"""
# If a download dir is specified, is the file already there and valid?
already_downloaded_path = None
if download_dir:
already_downloaded_path = _check_download_dir(
link, download_dir, hashes
)
if already_downloaded_path:
from_path = already_downloaded_path
else:
from_path = link.file_path
# If --require-hashes is off, `hashes` is either empty, the
# link's embedded hash, or MissingHashes; it is required to
# match. If --require-hashes is on, we are satisfied by any
# hash in `hashes` matching: a URL-based or an option-based
# one; no internet-sourced hash will be in `hashes`.
if hashes:
hashes.check_against_path(from_path)
content_type = mimetypes.guess_type(from_path)[0]
return File(from_path, content_type)
def unpack_url(
link, # type: Link
location, # type: str
downloader, # type: Downloader
download_dir=None, # type: Optional[str]
hashes=None, # type: Optional[Hashes]
):
# type: (...) -> Optional[File]
"""Unpack link into location, downloading if required.
:param hashes: A Hashes object, one of whose embedded hashes must match,
or HashMismatch will be raised. If the Hashes is empty, no matches are
required, and unhashable types of requirements (like VCS ones, which
would ordinarily raise HashUnsupported) are allowed.
"""
# non-editable vcs urls
if link.is_vcs:
unpack_vcs_link(link, location)
return None
# If it's a url to a local directory
if link.is_existing_dir():
if os.path.isdir(location):
rmtree(location)
_copy_source_tree(link.file_path, location)
return None
# file urls
if link.is_file:
file = get_file_url(link, download_dir, hashes=hashes)
# http urls
else:
file = get_http_url(
link,
downloader,
download_dir,
hashes=hashes,
)
# unpack the archive to the build dir location. even when only downloading
# archives, they have to be unpacked to parse dependencies, except wheels
if not link.is_wheel:
unpack_file(file.path, location, file.content_type)
return file
def _download_http_url(
link, # type: Link
downloader, # type: Downloader
temp_dir, # type: str
hashes, # type: Optional[Hashes]
):
# type: (...) -> Tuple[str, str]
"""Download link url into temp_dir using provided session"""
download = downloader(link)
file_path = os.path.join(temp_dir, download.filename)
with open(file_path, 'wb') as content_file:
for chunk in download.chunks:
content_file.write(chunk)
if hashes:
hashes.check_against_path(file_path)
return file_path, download.response.headers.get('content-type', '')
def _check_download_dir(link, download_dir, hashes):
# type: (Link, str, Optional[Hashes]) -> Optional[str]
""" Check download_dir for previously downloaded file with correct hash
If a correct file is found return its path else None
"""
download_path = os.path.join(download_dir, link.filename)
if not os.path.exists(download_path):
return None
# If already downloaded, does its hash match?
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.',
download_path
)
os.unlink(download_path)
return None
return download_path
class RequirementPreparer(object):
"""Prepares a Requirement
"""
@@ -45,9 +324,12 @@ class RequirementPreparer(object):
download_dir, # type: Optional[str]
src_dir, # type: str
wheel_download_dir, # type: Optional[str]
progress_bar, # type: str
build_isolation, # type: bool
req_tracker # type: RequirementTracker
req_tracker, # type: RequirementTracker
downloader, # type: Downloader
finder, # type: PackageFinder
require_hashes, # type: bool
use_user_site, # type: bool
):
# type: (...) -> None
super(RequirementPreparer, self).__init__()
@@ -55,16 +337,16 @@ class RequirementPreparer(object):
self.src_dir = src_dir
self.build_dir = build_dir
self.req_tracker = req_tracker
self.downloader = downloader
self.finder = finder
# Where still packed archives should be written to. If None, they are
# Where still-packed archives should be written to. If None, they are
# not saved, and are deleted immediately after unpacking.
self.download_dir = download_dir
# Where still-packed .whl files should be written to. If None, they are
# written to the download_dir parameter. Separate to download_dir to
# permit only keeping wheel archives for pip wheel.
if wheel_download_dir:
wheel_download_dir = normalize_path(wheel_download_dir)
self.wheel_download_dir = wheel_download_dir
# NOTE
@@ -72,160 +354,156 @@ class RequirementPreparer(object):
# be combined if we're willing to have non-wheel archives present in
# the wheelhouse output by 'pip wheel'.
self.progress_bar = progress_bar
# Is build isolation allowed?
self.build_isolation = build_isolation
# Should hash-checking be required?
self.require_hashes = require_hashes
# Should install in user site-packages?
self.use_user_site = use_user_site
@property
def _download_should_save(self):
# type: () -> bool
# TODO: Modify to reduce indentation needed
if self.download_dir:
self.download_dir = expanduser(self.download_dir)
if os.path.exists(self.download_dir):
return True
else:
logger.critical('Could not find download directory')
raise InstallationError(
"Could not find or access download directory '%s'"
% display_path(self.download_dir))
return False
if not self.download_dir:
return False
def prepare_linked_requirement(
self,
req, # type: InstallRequirement
session, # type: PipSession
finder, # type: PackageFinder
upgrade_allowed, # type: bool
require_hashes # type: bool
):
# type: (...) -> AbstractDistribution
"""Prepare a requirement that would be obtained from req.link
"""
# TODO: Breakup into smaller functions
if req.link and req.link.scheme == 'file':
path = url_to_path(req.link.url)
if os.path.exists(self.download_dir):
return True
logger.critical('Could not find download directory')
raise InstallationError(
"Could not find or access download directory '{}'"
.format(self.download_dir))
def _log_preparing_link(self, req):
# type: (InstallRequirement) -> None
"""Log the way the link prepared."""
if req.link.is_file:
path = req.link.file_path
logger.info('Processing %s', display_path(path))
else:
logger.info('Collecting %s', req)
logger.info('Collecting %s', req.req or req)
def _ensure_link_req_src_dir(self, req, download_dir, parallel_builds):
# type: (InstallRequirement, Optional[str], bool) -> None
"""Ensure source_dir of a linked InstallRequirement."""
# Since source_dir is only set for editable requirements.
if req.link.is_wheel:
# We don't need to unpack wheels, so no need for a source
# directory.
return
assert req.source_dir is None
# We always delete unpacked sdists after pip runs.
req.ensure_has_source_dir(
self.build_dir,
autodelete=True,
parallel_builds=parallel_builds,
)
# If a checkout exists, it's unwise to keep going. version
# inconsistencies are logged later, but do not fail the
# installation.
# FIXME: this won't upgrade when there's an existing
# package unpacked in `req.source_dir`
if os.path.exists(os.path.join(req.source_dir, 'setup.py')):
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)
)
def _get_linked_req_hashes(self, req):
# type: (InstallRequirement) -> Hashes
# By the time this is called, the requirement's link should have
# been checked so we can tell what kind of requirements req is
# and raise some more informative errors than otherwise.
# (For example, we can raise VcsHashUnsupported for a VCS URL
# rather than HashMissing.)
if not self.require_hashes:
return req.hashes(trust_internet=True)
# We could check these first 2 conditions inside unpack_url
# and save repetition of conditions, but then we would
# report less-useful error messages for unhashable
# requirements, complaining that there's no hash provided.
if req.link.is_vcs:
raise VcsHashUnsupported()
if req.link.is_existing_dir():
raise DirectoryUrlHashUnsupported()
# Unpinned packages are asking for trouble when a new version
# is uploaded. This isn't a security check, but it saves users
# a surprising hash mismatch in the future.
# file:/// URLs aren't pinnable, so don't complain about them
# not being pinned.
if req.original_link is None and not req.is_pinned:
raise HashUnpinned()
# If known-good hashes are missing for this requirement,
# shim it with a facade object that will provoke hash
# computation and then raise a HashMissing exception
# showing the user what the hash should be.
return req.hashes(trust_internet=False) or MissingHashes()
def prepare_linked_requirement(self, req, parallel_builds=False):
# type: (InstallRequirement, bool) -> AbstractDistribution
"""Prepare a requirement to be obtained from req.link."""
assert req.link
link = req.link
self._log_preparing_link(req)
if link.is_wheel and self.wheel_download_dir:
# Download wheels to a dedicated dir when doing `pip wheel`.
download_dir = self.wheel_download_dir
else:
download_dir = self.download_dir
with indent_log():
# @@ if filesystem packages are not marked
# editable in a req, a non deterministic error
# occurs when the script attempts to unpack the
# build directory
req.ensure_has_source_dir(self.build_dir)
# If a checkout exists, it's unwise to keep going. version
# inconsistencies are logged later, but do not fail the
# installation.
# FIXME: this won't upgrade when there's an existing
# package unpacked in `req.source_dir`
# package unpacked in `req.source_dir`
if os.path.exists(os.path.join(req.source_dir, 'setup.py')):
raise PreviousBuildDirError(
"pip can't proceed with requirements '%s' due to a"
" pre-existing build directory (%s). 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."
% (req, req.source_dir)
)
req.populate_link(finder, upgrade_allowed, require_hashes)
# We can't hit this spot and have populate_link return None.
# req.satisfied_by is None here (because we're
# guarded) and upgrade has no impact except when satisfied_by
# is not None.
# Then inside find_requirement existing_applicable -> False
# If no new versions are found, DistributionNotFound is raised,
# otherwise a result is guaranteed.
assert req.link
link = req.link
# Now that we have the real link, we can tell what kind of
# requirements we have and raise some more informative errors
# than otherwise. (For example, we can raise VcsHashUnsupported
# for a VCS URL rather than HashMissing.)
if require_hashes:
# We could check these first 2 conditions inside
# unpack_url and save repetition of conditions, but then
# we would report less-useful error messages for
# unhashable requirements, complaining that there's no
# hash provided.
if is_vcs_url(link):
raise VcsHashUnsupported()
elif is_file_url(link) and is_dir_url(link):
raise DirectoryUrlHashUnsupported()
if not req.original_link and not req.is_pinned:
# Unpinned packages are asking for trouble when a new
# version is uploaded. This isn't a security check, but
# it saves users a surprising hash mismatch in the
# future.
#
# file:/// URLs aren't pinnable, so don't complain
# about them not being pinned.
raise HashUnpinned()
hashes = req.hashes(trust_internet=not require_hashes)
if require_hashes and not hashes:
# Known-good hashes are missing for this requirement, so
# shim it with a facade object that will provoke hash
# computation and then raise a HashMissing exception
# showing the user what the hash should be.
hashes = MissingHashes()
self._ensure_link_req_src_dir(req, download_dir, parallel_builds)
try:
download_dir = self.download_dir
# We always delete unpacked sdists after pip ran.
autodelete_unpacked = True
if req.link.is_wheel and self.wheel_download_dir:
# when doing 'pip wheel` we download wheels to a
# dedicated dir.
download_dir = self.wheel_download_dir
if req.link.is_wheel:
if download_dir:
# When downloading, we only unpack wheels to get
# metadata.
autodelete_unpacked = True
else:
# When installing a wheel, we use the unpacked
# wheel.
autodelete_unpacked = False
unpack_url(
req.link, req.source_dir,
download_dir, autodelete_unpacked,
session=session, hashes=hashes,
progress_bar=self.progress_bar
)
except requests.HTTPError as exc:
logger.critical(
'Could not install requirement %s because of error %s',
req,
exc,
local_file = unpack_url(
link, req.source_dir, self.downloader, download_dir,
hashes=self._get_linked_req_hashes(req)
)
except NetworkConnectionError as exc:
raise InstallationError(
'Could not install requirement %s because of HTTP '
'error %s for URL %s' %
(req, exc, req.link)
)
abstract_dist = make_distribution_for_install_requirement(req)
with self.req_tracker.track(req):
abstract_dist.prepare_distribution_metadata(
finder, self.build_isolation,
'Could not install requirement {} because of HTTP '
'error {} for URL {}'.format(req, exc, link)
)
# For use in later processing, preserve the file path on the
# requirement.
if local_file:
req.local_file_path = local_file.path
abstract_dist = _get_prepared_distribution(
req, self.req_tracker, self.finder, self.build_isolation,
)
if download_dir:
if link.is_existing_dir():
logger.info('Link is a directory, ignoring download_dir')
elif local_file:
download_location = os.path.join(
download_dir, link.filename
)
if not os.path.exists(download_location):
shutil.copy(local_file.path, download_location)
download_path = display_path(download_location)
logger.info('Saved %s', download_path)
if self._download_should_save:
# Make a .zip of the source_dir we already created.
if not req.link.is_artifact:
if link.is_vcs:
req.archive(self.download_dir)
return abstract_dist
def prepare_editable_requirement(
self,
req, # type: InstallRequirement
require_hashes, # type: bool
use_user_site, # type: bool
finder # type: PackageFinder
):
# type: (...) -> AbstractDistribution
"""Prepare an editable requirement
@@ -235,31 +513,28 @@ class RequirementPreparer(object):
logger.info('Obtaining %s', req)
with indent_log():
if require_hashes:
if self.require_hashes:
raise InstallationError(
'The editable requirement %s cannot be installed when '
'The editable requirement {} cannot be installed when '
'requiring hashes, because there is no single file to '
'hash.' % req
'hash.'.format(req)
)
req.ensure_has_source_dir(self.src_dir)
req.update_editable(not self._download_should_save)
abstract_dist = make_distribution_for_install_requirement(req)
with self.req_tracker.track(req):
abstract_dist.prepare_distribution_metadata(
finder, self.build_isolation,
)
abstract_dist = _get_prepared_distribution(
req, self.req_tracker, self.finder, self.build_isolation,
)
if self._download_should_save:
req.archive(self.download_dir)
req.check_if_exists(use_user_site)
req.check_if_exists(self.use_user_site)
return abstract_dist
def prepare_installed_requirement(
self,
req, # type: InstallRequirement
require_hashes, # type: bool
skip_reason # type: str
):
# type: (...) -> AbstractDistribution
@@ -268,14 +543,14 @@ class RequirementPreparer(object):
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 %r" % (req.satisfied_by,)
"is set to {}".format(req.satisfied_by)
)
logger.info(
'Requirement %s: %s (%s)',
skip_reason, req, req.satisfied_by.version
)
with indent_log():
if require_hashes:
if self.require_hashes:
logger.debug(
'Since it is already installed, we are trusting this '
'package without checking its hash. To ensure a '

View File

@@ -1,384 +0,0 @@
"""Generate and work with PEP 425 Compatibility Tags."""
from __future__ import absolute_import
import distutils.util
import logging
import platform
import re
import sys
import sysconfig
import warnings
from collections import OrderedDict
import pip._internal.utils.glibc
from pip._internal.utils.compat import get_extension_suffixes
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import (
Tuple, Callable, List, Optional, Union, Dict
)
Pep425Tag = Tuple[str, str, str]
logger = logging.getLogger(__name__)
_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)')
def get_config_var(var):
# type: (str) -> Optional[str]
try:
return sysconfig.get_config_var(var)
except IOError as e: # Issue #1074
warnings.warn("{}".format(e), RuntimeWarning)
return None
def get_abbr_impl():
# type: () -> str
"""Return abbreviated implementation name."""
if hasattr(sys, 'pypy_version_info'):
pyimpl = 'pp'
elif sys.platform.startswith('java'):
pyimpl = 'jy'
elif sys.platform == 'cli':
pyimpl = 'ip'
else:
pyimpl = 'cp'
return pyimpl
def version_info_to_nodot(version_info):
# type: (Tuple[int, ...]) -> str
# Only use up to the first two numbers.
return ''.join(map(str, version_info[:2]))
def get_impl_ver():
# type: () -> str
"""Return implementation version."""
impl_ver = get_config_var("py_version_nodot")
if not impl_ver or get_abbr_impl() == 'pp':
impl_ver = ''.join(map(str, get_impl_version_info()))
return impl_ver
def get_impl_version_info():
# type: () -> Tuple[int, ...]
"""Return sys.version_info-like tuple for use in decrementing the minor
version."""
if get_abbr_impl() == 'pp':
# as per https://github.com/pypa/pip/issues/2882
# attrs exist only on pypy
return (sys.version_info[0],
sys.pypy_version_info.major, # type: ignore
sys.pypy_version_info.minor) # type: ignore
else:
return sys.version_info[0], sys.version_info[1]
def get_impl_tag():
# type: () -> str
"""
Returns the Tag for this specific implementation.
"""
return "{}{}".format(get_abbr_impl(), get_impl_ver())
def get_flag(var, fallback, expected=True, warn=True):
# type: (str, Callable[..., bool], Union[bool, int], bool) -> bool
"""Use a fallback method for determining SOABI flags if the needed config
var is unset or unavailable."""
val = get_config_var(var)
if val is None:
if warn:
logger.debug("Config variable '%s' is unset, Python ABI tag may "
"be incorrect", var)
return fallback()
return val == expected
def get_abi_tag():
# type: () -> Optional[str]
"""Return the ABI tag based on SOABI (if available) or emulate SOABI
(CPython 2, PyPy)."""
soabi = get_config_var('SOABI')
impl = get_abbr_impl()
if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'):
d = ''
m = ''
u = ''
is_cpython = (impl == 'cp')
if get_flag(
'Py_DEBUG', lambda: hasattr(sys, 'gettotalrefcount'),
warn=is_cpython):
d = 'd'
if sys.version_info < (3, 8) and get_flag(
'WITH_PYMALLOC', lambda: is_cpython, warn=is_cpython):
m = 'm'
if sys.version_info < (3, 3) and get_flag(
'Py_UNICODE_SIZE', lambda: sys.maxunicode == 0x10ffff,
expected=4, warn=is_cpython):
u = 'u'
abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u)
elif soabi and soabi.startswith('cpython-'):
abi = 'cp' + soabi.split('-')[1]
elif soabi:
abi = soabi.replace('.', '_').replace('-', '_')
else:
abi = None
return abi
def _is_running_32bit():
# type: () -> bool
return sys.maxsize == 2147483647
def get_platform():
# type: () -> str
"""Return our platform name 'win32', 'linux_x86_64'"""
if sys.platform == 'darwin':
# distutils.util.get_platform() returns the release based on the value
# of MACOSX_DEPLOYMENT_TARGET on which Python was built, which may
# be significantly older than the user's current machine.
release, _, machine = platform.mac_ver()
split_ver = release.split('.')
if machine == "x86_64" and _is_running_32bit():
machine = "i386"
elif machine == "ppc64" and _is_running_32bit():
machine = "ppc"
return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine)
# XXX remove distutils dependency
result = distutils.util.get_platform().replace('.', '_').replace('-', '_')
if result == "linux_x86_64" and _is_running_32bit():
# 32 bit Python program (running on a 64 bit Linux): pip should only
# install and run 32 bit compiled extensions in that case.
result = "linux_i686"
return result
def is_manylinux1_compatible():
# type: () -> bool
# Only Linux, and only x86-64 / i686
if get_platform() not in {"linux_x86_64", "linux_i686"}:
return False
# Check for presence of _manylinux module
try:
import _manylinux
return bool(_manylinux.manylinux1_compatible)
except (ImportError, AttributeError):
# Fall through to heuristic check below
pass
# Check glibc version. CentOS 5 uses glibc 2.5.
return pip._internal.utils.glibc.have_compatible_glibc(2, 5)
def is_manylinux2010_compatible():
# type: () -> bool
# Only Linux, and only x86-64 / i686
if get_platform() not in {"linux_x86_64", "linux_i686"}:
return False
# Check for presence of _manylinux module
try:
import _manylinux
return bool(_manylinux.manylinux2010_compatible)
except (ImportError, AttributeError):
# Fall through to heuristic check below
pass
# Check glibc version. CentOS 6 uses glibc 2.12.
return pip._internal.utils.glibc.have_compatible_glibc(2, 12)
def get_darwin_arches(major, minor, machine):
# type: (int, int, str) -> List[str]
"""Return a list of supported arches (including group arches) for
the given major, minor and machine architecture of an macOS machine.
"""
arches = []
def _supports_arch(major, minor, arch):
# type: (int, int, str) -> bool
# Looking at the application support for macOS versions in the chart
# provided by https://en.wikipedia.org/wiki/OS_X#Versions it appears
# our timeline looks roughly like:
#
# 10.0 - Introduces ppc support.
# 10.4 - Introduces ppc64, i386, and x86_64 support, however the ppc64
# and x86_64 support is CLI only, and cannot be used for GUI
# applications.
# 10.5 - Extends ppc64 and x86_64 support to cover GUI applications.
# 10.6 - Drops support for ppc64
# 10.7 - Drops support for ppc
#
# Given that we do not know if we're installing a CLI or a GUI
# application, we must be conservative and assume it might be a GUI
# application and behave as if ppc64 and x86_64 support did not occur
# until 10.5.
#
# Note: The above information is taken from the "Application support"
# column in the chart not the "Processor support" since I believe
# that we care about what instruction sets an application can use
# not which processors the OS supports.
if arch == 'ppc':
return (major, minor) <= (10, 5)
if arch == 'ppc64':
return (major, minor) == (10, 5)
if arch == 'i386':
return (major, minor) >= (10, 4)
if arch == 'x86_64':
return (major, minor) >= (10, 5)
if arch in groups:
for garch in groups[arch]:
if _supports_arch(major, minor, garch):
return True
return False
groups = OrderedDict([
("fat", ("i386", "ppc")),
("intel", ("x86_64", "i386")),
("fat64", ("x86_64", "ppc64")),
("fat32", ("x86_64", "i386", "ppc")),
]) # type: Dict[str, Tuple[str, ...]]
if _supports_arch(major, minor, machine):
arches.append(machine)
for garch in groups:
if machine in groups[garch] and _supports_arch(major, minor, garch):
arches.append(garch)
arches.append('universal')
return arches
def get_all_minor_versions_as_strings(version_info):
# type: (Tuple[int, ...]) -> List[str]
versions = []
major = version_info[:-1]
# Support all previous minor Python versions.
for minor in range(version_info[-1], -1, -1):
versions.append(''.join(map(str, major + (minor,))))
return versions
def get_supported(
versions=None, # type: Optional[List[str]]
noarch=False, # type: bool
platform=None, # type: Optional[str]
impl=None, # type: Optional[str]
abi=None # type: Optional[str]
):
# type: (...) -> List[Pep425Tag]
"""Return a list of supported tags for each version specified in
`versions`.
:param versions: a list of string versions, of the form ["33", "32"],
or None. The first version will be assumed to support our ABI.
:param platform: specify the exact platform you want valid
tags for, or None. If None, use the local system platform.
:param impl: specify the exact implementation you want valid
tags for, or None. If None, use the local interpreter impl.
:param abi: specify the exact abi you want valid
tags for, or None. If None, use the local interpreter abi.
"""
supported = []
# Versions must be given with respect to the preference
if versions is None:
version_info = get_impl_version_info()
versions = get_all_minor_versions_as_strings(version_info)
impl = impl or get_abbr_impl()
abis = [] # type: List[str]
abi = abi or get_abi_tag()
if abi:
abis[0:0] = [abi]
abi3s = set()
for suffix in get_extension_suffixes():
if suffix.startswith('.abi'):
abi3s.add(suffix.split('.', 2)[1])
abis.extend(sorted(list(abi3s)))
abis.append('none')
if not noarch:
arch = platform or get_platform()
arch_prefix, arch_sep, arch_suffix = arch.partition('_')
if arch.startswith('macosx'):
# support macosx-10.6-intel on macosx-10.9-x86_64
match = _osx_arch_pat.match(arch)
if match:
name, major, minor, actual_arch = match.groups()
tpl = '{}_{}_%i_%s'.format(name, major)
arches = []
for m in reversed(range(int(minor) + 1)):
for a in get_darwin_arches(int(major), m, actual_arch):
arches.append(tpl % (m, a))
else:
# arch pattern didn't match (?!)
arches = [arch]
elif arch_prefix == 'manylinux2010':
# manylinux1 wheels run on most manylinux2010 systems with the
# exception of wheels depending on ncurses. PEP 571 states
# manylinux1 wheels should be considered manylinux2010 wheels:
# https://www.python.org/dev/peps/pep-0571/#backwards-compatibility-with-manylinux1-wheels
arches = [arch, 'manylinux1' + arch_sep + arch_suffix]
elif platform is None:
arches = []
if is_manylinux2010_compatible():
arches.append('manylinux2010' + arch_sep + arch_suffix)
if is_manylinux1_compatible():
arches.append('manylinux1' + arch_sep + arch_suffix)
arches.append(arch)
else:
arches = [arch]
# Current version, current API (built specifically for our Python):
for abi in abis:
for arch in arches:
supported.append(('%s%s' % (impl, versions[0]), abi, arch))
# abi3 modules compatible with older version of Python
for version in versions[1:]:
# abi3 was introduced in Python 3.2
if version in {'31', '30'}:
break
for abi in abi3s: # empty set if not Python 3
for arch in arches:
supported.append(("%s%s" % (impl, version), abi, arch))
# Has binaries, does not use the Python API:
for arch in arches:
supported.append(('py%s' % (versions[0][0]), 'none', arch))
# No abi / arch, but requires our implementation:
supported.append(('%s%s' % (impl, versions[0]), 'none', 'any'))
# Tagged specifically as being cross-version compatible
# (with just the major version specified)
supported.append(('%s%s' % (impl, versions[0][0]), 'none', 'any'))
# No abi / arch, generic Python
for i, version in enumerate(versions):
supported.append(('py%s' % (version,), 'none', 'any'))
if i == 0:
supported.append(('py%s' % (version[0]), 'none', 'any'))
return supported
implementation_tag = get_impl_tag()

View File

@@ -3,14 +3,16 @@ from __future__ import absolute_import
import io
import os
import sys
from collections import namedtuple
from pip._vendor import pytoml, six
from pip._vendor import six, toml
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
from pip._internal.exceptions import InstallationError
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import Any, Tuple, Optional, List
from typing import Any, Optional, List
def _is_list_of_str(obj):
@@ -21,9 +23,9 @@ def _is_list_of_str(obj):
)
def make_pyproject_path(setup_py_dir):
def make_pyproject_path(unpacked_source_directory):
# type: (str) -> str
path = os.path.join(setup_py_dir, 'pyproject.toml')
path = os.path.join(unpacked_source_directory, 'pyproject.toml')
# Python2 __file__ should not be unicode
if six.PY2 and isinstance(path, six.text_type):
@@ -32,13 +34,18 @@ def make_pyproject_path(setup_py_dir):
return path
BuildSystemDetails = namedtuple('BuildSystemDetails', [
'requires', 'backend', 'check', 'backend_path'
])
def load_pyproject_toml(
use_pep517, # type: Optional[bool]
pyproject_toml, # type: str
setup_py, # type: str
req_name # type: str
):
# type: (...) -> Optional[Tuple[List[str], str, List[str]]]
# type: (...) -> Optional[BuildSystemDetails]
"""Load the pyproject.toml file.
Parameters:
@@ -56,6 +63,8 @@ def load_pyproject_toml(
name of PEP 517 backend,
requirements we should check are installed after setting
up the build environment
directory paths to import the backend from (backend-path),
relative to the project root.
)
"""
has_pyproject = os.path.isfile(pyproject_toml)
@@ -63,7 +72,7 @@ def load_pyproject_toml(
if has_pyproject:
with io.open(pyproject_toml, encoding="utf-8") as f:
pp_toml = pytoml.load(f)
pp_toml = toml.load(f)
build_system = pp_toml.get("build-system")
else:
build_system = None
@@ -150,7 +159,23 @@ def load_pyproject_toml(
reason="'build-system.requires' is not a list of strings.",
))
# Each requirement must be valid as per PEP 508
for requirement in requires:
try:
Requirement(requirement)
except InvalidRequirement:
raise InstallationError(
error_template.format(
package=req_name,
reason=(
"'build-system.requires' contains an invalid "
"requirement: {!r}".format(requirement)
),
)
)
backend = build_system.get("build-backend")
backend_path = build_system.get("backend-path", [])
check = [] # type: List[str]
if backend is None:
# If the user didn't specify a backend, we assume they want to use
@@ -168,4 +193,4 @@ def load_pyproject_toml(
backend = "setuptools.build_meta:__legacy__"
check = ["setuptools>=40.8.0", "wheel"]
return (requires, backend, check)
return BuildSystemDetails(requires, backend, check, backend_path)

View File

@@ -1,15 +1,17 @@
from __future__ import absolute_import
import collections
import logging
from .req_install import InstallRequirement
from .req_set import RequirementSet
from .req_file import parse_requirements
from pip._internal.utils.logging import indent_log
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from .req_file import parse_requirements
from .req_install import InstallRequirement
from .req_set import RequirementSet
if MYPY_CHECK_RUNNING:
from typing import Any, List, Sequence
from typing import Iterator, List, Optional, Sequence, Tuple
__all__ = [
"RequirementSet", "InstallRequirement",
@@ -19,60 +21,83 @@ __all__ = [
logger = logging.getLogger(__name__)
def install_given_reqs(
to_install, # type: List[InstallRequirement]
install_options, # type: List[str]
global_options=(), # type: Sequence[str]
*args, # type: Any
**kwargs # type: Any
class InstallationResult(object):
def __init__(self, name):
# type: (str) -> None
self.name = name
def __repr__(self):
# type: () -> str
return "InstallationResult(name={!r})".format(self.name)
def _validate_requirements(
requirements, # type: List[InstallRequirement]
):
# type: (...) -> List[InstallRequirement]
# type: (...) -> Iterator[Tuple[str, InstallRequirement]]
for req in requirements:
assert req.name, "invalid to-be-installed requirement: {}".format(req)
yield req.name, req
def install_given_reqs(
requirements, # type: List[InstallRequirement]
install_options, # type: List[str]
global_options, # type: Sequence[str]
root, # type: Optional[str]
home, # type: Optional[str]
prefix, # type: Optional[str]
warn_script_location, # type: bool
use_user_site, # type: bool
pycompile, # type: bool
):
# type: (...) -> List[InstallationResult]
"""
Install everything in the given list.
(to be called after having downloaded and unpacked the packages)
"""
to_install = collections.OrderedDict(_validate_requirements(requirements))
if to_install:
logger.info(
'Installing collected packages: %s',
', '.join([req.name for req in to_install]),
', '.join(to_install.keys()),
)
installed = []
with indent_log():
for requirement in to_install:
if requirement.conflicts_with:
logger.info(
'Found existing installation: %s',
requirement.conflicts_with,
)
for req_name, requirement in to_install.items():
if requirement.should_reinstall:
logger.info('Attempting uninstall: %s', req_name)
with indent_log():
uninstalled_pathset = requirement.uninstall(
auto_confirm=True
)
else:
uninstalled_pathset = None
try:
requirement.install(
install_options,
global_options,
*args,
**kwargs
root=root,
home=home,
prefix=prefix,
warn_script_location=warn_script_location,
use_user_site=use_user_site,
pycompile=pycompile,
)
except Exception:
should_rollback = (
requirement.conflicts_with and
not requirement.install_succeeded
)
# if install did not succeed, rollback previous uninstall
if should_rollback:
if uninstalled_pathset and not requirement.install_succeeded:
uninstalled_pathset.rollback()
raise
else:
should_commit = (
requirement.conflicts_with and
requirement.install_succeeded
)
if should_commit:
if uninstalled_pathset and requirement.install_succeeded:
uninstalled_pathset.commit()
requirement.remove_temporary_source()
return to_install
installed.append(InstallationResult(req_name))
return installed

Some files were not shown because too many files have changed in this diff Show More