未验证 提交 0839db55 编写于 作者: P Pradyun Gedam

Merge branch 'master' into release/19.2.2

environment:
matrix:
# Unit and integration tests.
- PYTHON: "C:\\Python27"
- PYTHON: "C:\\Python27-x64"
RUN_INTEGRATION_TESTS: "True"
- PYTHON: "C:\\Python35-x64"
RUN_INTEGRATION_TESTS: "True"
- PYTHON: "C:\\Python36-x64"
RUN_INTEGRATION_TESTS: "True"
# Unit tests only.
- PYTHON: "C:\\Python27-x64"
- PYTHON: "C:\\Python35"
- PYTHON: "C:\\Python35-x64"
- PYTHON: "C:\\Python36"
# Nothing for the moment
matrix:
fast_finish: true
......@@ -65,9 +64,6 @@ test_script:
subst T: $env:TEMP
$env:TEMP = "T:\"
$env:TMP = "T:\"
# Workaround warnings traced in packaging.requirements with pyparsing 2.4.1.
# See pypa/packaging#170 and tox-dev/tox#1375.
$env:PYTHONWARNINGS = "ignore:warn_ungrouped_named_tokens_in_collection"
tox -e py -- -m unit
if ($LastExitCode -eq 0 -and $env:RUN_INTEGRATION_TESTS -eq "True") {
tox -e py -- --use-venv -m integration -n2 --durations=20
......
......@@ -9,11 +9,11 @@ jobs:
vmImage: ${{ parameters.vmImage }}
strategy:
matrix:
Python27-x64:
Python27-x86:
python.version: '2.7'
python.architecture: x64
Python36-x64:
python.version: '3.6'
python.architecture: x86
Python37-x64:
python.version: '3.7'
python.architecture: x64
maxParallel: 2
......@@ -32,16 +32,7 @@ jobs:
vmImage: ${{ parameters.vmImage }}
strategy:
matrix:
Python35-x64:
python.version: '3.5'
python.architecture: x64
Python37-x64:
python.version: '3.7'
python.architecture: x64
# This is for Windows, so test x86 builds
Python27-x86:
python.version: '2.7'
python.architecture: x86
Python35-x86:
python.version: '3.5'
python.architecture: x86
......
version: 2
sphinx:
builder: htmldir
configuration: docs/html/conf.py
python:
version: 3.7
install:
- requirements: tools/requirements/docs.txt
language: python
cache: pip
dist: xenial
python: 3.6
python: 3.7
addons:
apt:
packages:
......@@ -27,9 +27,7 @@ jobs:
- env: GROUP=2
python: 2.7
- env: GROUP=1
python: 3.6
- env: GROUP=2
python: 3.6
# Complete checking for ensuring compatibility
# PyPy
......@@ -44,9 +42,9 @@ jobs:
python: pypy2.7-6.0
# Other Supported CPython
- env: GROUP=1
python: 3.7
python: 3.6
- env: GROUP=2
python: 3.7
python: 3.6
- env: GROUP=1
python: 3.5
- env: GROUP=2
......
......@@ -15,6 +15,7 @@ exclude .coveragerc
exclude .mailmap
exclude .appveyor.yml
exclude .travis.yml
exclude .readthedocs.yml
exclude tox.ini
recursive-include src/pip/_vendor *.pem
......
===============================
Architecture of pip's internals
===============================
.. note::
This section of the documentation is currently being written. pip
developers welcome your help to complete this documentation. If you're
interested in helping out, please let us know in the `tracking issue`_.
.. note::
Direct use of pip's internals is *not supported*.
For more details, see :ref:`Using pip from your program`.
.. toctree::
:maxdepth: 2
.. _`tracking issue`: https://github.com/pypa/pip/issues/6831
......@@ -14,6 +14,7 @@ or the `pypa-dev mailing list`_, to ask questions or get involved.
getting-started
contributing
architecture/index
release-process
vendoring-policy
......
......@@ -694,10 +694,21 @@ does not satisfy the ``--require-hashes`` demand that every package have a
local hash.
Local project installs
++++++++++++++++++++++
pip supports installing local project in both regular mode and editable mode.
You can install local projects by specifying the project path to pip::
$ pip install path/to/SomeProject
During regular installation, pip will copy the entire project directory to a temporary location and install from there.
The exception is that pip will exclude .tox and .nox directories present in the top level of the project from being copied.
.. _`editable-installs`:
"Editable" Installs
+++++++++++++++++++
~~~~~~~~~~~~~~~~~~~
"Editable" installs are fundamentally `"setuptools develop mode"
<https://setuptools.readthedocs.io/en/latest/setuptools.html#development-mode>`_
......
......@@ -9,14 +9,14 @@ from docutils.parsers import rst
from docutils.statemachine import ViewList
from pip._internal.cli import cmdoptions
from pip._internal.commands import commands_dict as commands
from pip._internal.commands import create_command
class PipCommandUsage(rst.Directive):
required_arguments = 1
def run(self):
cmd = commands[self.arguments[0]]
cmd = create_command(self.arguments[0])
usage = dedent(
cmd.usage.replace('%prog', 'pip {}'.format(cmd.name))
).strip()
......@@ -31,7 +31,8 @@ class PipCommandDescription(rst.Directive):
node = nodes.paragraph()
node.document = self.state.document
desc = ViewList()
description = dedent(commands[self.arguments[0]].__doc__)
cmd = create_command(self.arguments[0])
description = dedent(cmd.__doc__)
for line in description.split('\n'):
desc.append(line, "")
self.state.nested_parse(desc, 0, node)
......@@ -95,7 +96,7 @@ class PipCommandOptions(PipOptions):
required_arguments = 1
def process_options(self):
cmd = commands[self.arguments[0]]()
cmd = create_command(self.arguments[0])
self._format_options(
cmd.parser.option_groups[0].option_list,
cmd_name=cmd.name,
......
pip's CLI completion code no longer prints a Traceback if it is interrupted.
Document caveats for UNC paths in uninstall and add .pth unit tests.
Skip copying .tox and .nox directories to temporary build directories
Document that ``--ignore-installed`` is dangerous.
Fix handling of tokens (single part credentials) in URLs.
Fix a regression that caused ``~`` expansion not to occur in ``--find-links``
paths.
......@@ -4,7 +4,7 @@ skip =
.scratch,
_vendor,
data
multi_line_output = 5
multi_line_output = 3
known_third_party =
pip._vendor
known_first_party =
......@@ -26,81 +26,6 @@ ignore = W504
follow_imports = silent
ignore_missing_imports = True
[mypy-pip/_internal/build_env]
strict_optional = False
[mypy-pip/_internal/cache]
strict_optional = False
[mypy-pip/_internal/cli/base_command]
strict_optional = False
[mypy-pip/_internal/cli/cmdoptions]
strict_optional = False
[mypy-pip/_internal/configuration]
strict_optional = False
[mypy-pip/_internal/index]
strict_optional = False
[mypy-pip/_internal/legacy_resolve]
strict_optional = False
[mypy-pip/_internal/locations]
strict_optional = False
[mypy-pip/_internal/models/format_control]
strict_optional = False
[mypy-pip/_internal/operations/check]
strict_optional = False
[mypy-pip/_internal/operations/freeze]
strict_optional = False
[mypy-pip/_internal/operations/prepare]
strict_optional = False
[mypy-pip/_internal/pep425tags]
strict_optional = False
[mypy-pip/_internal/req/*]
disallow_untyped_defs = True
[mypy-pip/_internal/req]
strict_optional = False
[mypy-pip/_internal/req/constructors]
strict_optional = False
[mypy-pip/_internal/req/req_file]
strict_optional = False
[mypy-pip/_internal/req/req_install]
strict_optional = False
[mypy-pip/_internal/req/req_set]
strict_optional = False
[mypy-pip/_internal/req/req_tracker]
strict_optional = False
[mypy-pip/_internal/utils/encoding]
strict_optional = False
[mypy-pip/_internal/utils/glibc]
strict_optional = False
[mypy-pip/_internal/utils/misc]
strict_optional = False
[mypy-pip/_internal/utils/ui]
strict_optional = False
[mypy-pip/_internal/wheel]
strict_optional = False
[mypy-pip/_vendor/*]
follow_imports = skip
ignore_errors = True
......
__version__ = "19.2.2"
__version__ = "19.3.dev0"
......@@ -4,50 +4,31 @@ from __future__ import absolute_import
import locale
import logging
import os
import warnings
import sys
import warnings
# 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()
# We ignore certain warnings from urllib3, since they are not relevant to pip's
# usecases.
from pip._vendor.urllib3.exceptions import (
DependencyWarning,
InsecureRequestWarning,
)
import pip._internal.utils.inject_securetransport # noqa
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.commands import create_command
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
# Raised when using --trusted-host.
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
# Raised since socks support depends on PySocks, which may not be installed.
# Barry Warsaw noted (on 2016-06-17) that this should be done before
# importing pip.vcs, which has since moved to pip._internal.vcs.
warnings.filterwarnings("ignore", category=DependencyWarning)
logger = logging.getLogger(__name__)
def main(args=None):
......@@ -73,5 +54,6 @@ def main(args=None):
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))
command = create_command(cmd_name, isolated=("--isolated" in cmd_args))
return command.main(cmd_args)
"""Build Environment used for isolation during sdist building
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import logging
import os
import sys
......
"""Cache Management
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import errno
import hashlib
import logging
......@@ -18,6 +21,7 @@ from pip._internal.wheel import InvalidWheelFilename, Wheel
if MYPY_CHECK_RUNNING:
from typing import Optional, Set, List, Any
from pip._internal.index import FormatControl
from pip._internal.pep425tags import Pep425Tag
logger = logging.getLogger(__name__)
......@@ -100,8 +104,13 @@ 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[Pep425Tag]
):
# type: (...) -> Link
"""Returns a link to a cached item if it exists, otherwise returns the
passed link.
"""
......@@ -150,8 +159,13 @@ class SimpleWheelCache(Cache):
# 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[Pep425Tag]
):
# type: (...) -> Link
candidates = []
for wheel_name in self._get_candidates(link, package_name):
......@@ -159,10 +173,12 @@ class SimpleWheelCache(Cache):
wheel = Wheel(wheel_name)
except InvalidWheelFilename:
continue
if not wheel.supported():
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)
)
if not candidates:
return link
......@@ -211,12 +227,26 @@ 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[Pep425Tag]
):
# type: (...) -> Link
retval = self._wheel_cache.get(
link=link,
package_name=package_name,
supported_tags=supported_tags,
)
if retval is not link:
return retval
return self._ephem_cache.get(
link=link,
package_name=package_name,
supported_tags=supported_tags,
)
def cleanup(self):
# type: () -> None
......
......@@ -6,7 +6,7 @@ import os
import sys
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
......@@ -23,7 +23,7 @@ def autocomplete():
except IndexError:
current = ''
subcommands = [cmd for cmd, summary in get_summaries()]
subcommands = list(commands_dict)
options = []
# subcommand
try:
......@@ -54,7 +54,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:
......
"""Base Command class, and related routines"""
from __future__ import absolute_import, print_function
import logging
......@@ -10,38 +11,33 @@ import sys
import traceback
from pip._internal.cli import cmdoptions
from pip._internal.cli.cmdoptions import make_search_scope
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,
SUCCESS,
UNKNOWN_ERROR,
VIRTUALENV_NOT_FOUND,
)
from pip._internal.download import PipSession
from pip._internal.exceptions import (
BadCommand, CommandError, InstallationError, PreviousBuildDirError,
BadCommand,
CommandError,
InstallationError,
PreviousBuildDirError,
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.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.misc import get_prog
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, Tuple, Any
from optparse import Values
from pip._internal.cache import WheelCache
from pip._internal.req.req_set import RequirementSet
__all__ = ['Command']
......@@ -49,22 +45,23 @@ logger = logging.getLogger(__name__)
class Command(object):
name = None # type: Optional[str]
usage = None # type: Optional[str]
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
parser_kw = {
'usage': self.usage,
'prog': '%s %s' % (get_prog(), self.name),
'prog': '%s %s' % (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)
# Commands should add options to this option group
......@@ -78,62 +75,20 @@ class Command(object):
)
self.parser.add_option_group(gen_opts)
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
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
# factored out for testability
......@@ -223,124 +178,9 @@ 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)
self.handle_pip_version_check(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,
)
......@@ -5,8 +5,11 @@ 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
......
......@@ -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 = [''] + [
'%-27s %s' % (name, command_info.summary)
for name, command_info in commands_dict.items()
]
parser.description = '\n'.join(description)
return parser
......
"""Contains the Command base classes that depend on PipSession.
The classes in this module are in a separate module so the commands not
needing download / PackageFinder capability don't unnecessarily import the
PackageFinder machinery and all its vendored dependencies, etc.
"""
import os
from pip._internal.cli.base_command import Command
from pip._internal.cli.cmdoptions import make_search_scope
from pip._internal.download import PipSession
from pip._internal.exceptions import CommandError
from pip._internal.index import PackageFinder
from pip._internal.legacy_resolve import Resolver
from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.operations.prepare import RequirementPreparer
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.misc import normalize_path
from pip._internal.utils.outdated import pip_version_check
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import List, Optional, Tuple
from pip._internal.cache import WheelCache
from pip._internal.models.target_python import TargetPython
from pip._internal.req.req_set import RequirementSet
from pip._internal.req.req_tracker import RequirementTracker
from pip._internal.utils.temp_dir import TempDirectory
class SessionCommandMixin(object):
"""
A class mixin for command classes needing _build_session().
"""
@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
class IndexGroupCommand(SessionCommandMixin, Command):
"""
Abstract base class for commands with the index_group options.
This also corresponds to the commands that permit the pip version check.
"""
def handle_pip_version_check(self, options):
# type: (Values) -> None
"""
Do the pip version check if not disabled.
This overrides the default behavior of not doing the check.
"""
# Make sure the index_group options are present.
assert hasattr(options, 'no_index')
if options.disable_pip_version_check or options.no_index:
return
# Otherwise, check if we're using the latest version of pip available.
session = self._build_session(
options,
retries=0,
timeout=min(5, options.timeout)
)
with session:
pip_version_check(session, options)
class RequirementCommand(IndexGroupCommand):
@staticmethod
def make_requirement_preparer(
temp_directory, # type: TempDirectory
options, # type: Values
req_tracker, # type: RequirementTracker
download_dir=None, # type: str
wheel_download_dir=None, # type: str
):
# type: (...) -> RequirementPreparer
"""
Create a RequirementPreparer instance for the given parameters.
"""
return RequirementPreparer(
build_dir=temp_directory.path,
src_dir=options.src_dir,
download_dir=download_dir,
wheel_download_dir=wheel_download_dir,
progress_bar=options.progress_bar,
build_isolation=options.build_isolation,
req_tracker=req_tracker,
)
@staticmethod
def make_resolver(
preparer, # type: RequirementPreparer
session, # type: PipSession
finder, # type: PackageFinder
options, # type: Values
wheel_cache=None, # type: Optional[WheelCache]
use_user_site=False, # type: bool
ignore_installed=True, # type: bool
ignore_requires_python=False, # type: bool
force_reinstall=False, # type: bool
upgrade_strategy="to-satisfy-only", # type: str
use_pep517=None, # type: Optional[bool]
py_version_info=None # type: Optional[Tuple[int, ...]]
):
# type: (...) -> Resolver
"""
Create a Resolver instance for the given parameters.
"""
return Resolver(
preparer=preparer,
session=session,
finder=finder,
wheel_cache=wheel_cache,
use_user_site=use_user_site,
ignore_dependencies=options.ignore_dependencies,
ignore_installed=ignore_installed,
ignore_requires_python=ignore_requires_python,
force_reinstall=force_reinstall,
isolated=options.isolated_mode,
upgrade_strategy=upgrade_strategy,
use_pep517=use_pep517,
py_version_info=py_version_info
)
def populate_requirement_set(
self,
requirement_set, # type: RequirementSet
args, # type: List[str]
options, # type: Values
finder, # type: PackageFinder
session, # type: PipSession
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': self.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,
)
......@@ -3,57 +3,97 @@ Package containing all pip commands
"""
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 namedtuple, OrderedDict
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}
def get_summaries(ordered=True):
"""Yields sorted (command name, command summary) tuples."""
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)
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.',
)),
('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 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)
return command
def get_similar_commands(name):
......@@ -68,14 +108,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)
......@@ -2,7 +2,8 @@ import logging
from pip._internal.cli.base_command import Command
from pip._internal.operations.check import (
check_package_set, create_package_set_from_installed,
check_package_set,
create_package_set_from_installed,
)
logger = logging.getLogger(__name__)
......@@ -10,10 +11,9 @@ logger = logging.getLogger(__name__)
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):
package_set, parsing_probs = create_package_set_from_installed()
......
......@@ -16,7 +16,7 @@ COMPLETION_SCRIPTS = {
{
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
COMP_CWORD=$COMP_CWORD \\
PIP_AUTO_COMPLETE=1 $1 ) )
PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) )
}
complete -o default -F _pip_completion %(prog)s
""",
......@@ -27,7 +27,7 @@ COMPLETION_SCRIPTS = {
read -cn cword
reply=( $( COMP_WORDS="$words[*]" \\
COMP_CWORD=$(( cword-1 )) \\
PIP_AUTO_COMPLETE=1 $words[1] ) )
PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null ))
}
compctl -K _pip_completion %(prog)s
""",
......@@ -47,8 +47,7 @@ COMPLETION_SCRIPTS = {
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):
......
......@@ -5,7 +5,9 @@ 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
......@@ -32,7 +34,6 @@ class ConfigurationCommand(Command):
default.
"""
name = 'config'
usage = """
%prog [<file-option>] list
%prog [<file-option>] [--editor <editor-path>] edit
......@@ -42,8 +43,6 @@ class ConfigurationCommand(Command):
%prog [<file-option>] unset name
"""
summary = "Manage local and global configuration."
def __init__(self, *args, **kwargs):
super(ConfigurationCommand, self).__init__(*args, **kwargs)
......
......@@ -77,10 +77,8 @@ 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):
......
......@@ -4,10 +4,8 @@ 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.cli.req_command import RequirementCommand
from pip._internal.req import RequirementSet
from pip._internal.req.req_tracker import RequirementTracker
from pip._internal.utils.filesystem import check_path_owner
......@@ -29,7 +27,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,8 +35,6 @@ class DownloadCommand(RequirementCommand):
%prog [options] <local project path> ...
%prog [options] <archive url/path> ..."""
summary = 'Download packages.'
def __init__(self, *args, **kw):
super(DownloadCommand, self).__init__(*args, **kw)
......@@ -125,33 +120,22 @@ class DownloadCommand(RequirementCommand):
options,
finder,
session,
self.name,
None
)
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,
preparer = self.make_requirement_preparer(
temp_directory=directory,
options=options,
req_tracker=req_tracker,
download_dir=options.download_dir,
)
resolver = Resolver(
resolver = self.make_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,
options=options,
py_version_info=options.python_version,
ignore_requires_python=False,
ignore_installed=True,
isolated=options.isolated_mode,
)
resolver.resolve(requirement_set)
......
......@@ -18,10 +18,9 @@ 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):
......
......@@ -18,11 +18,9 @@ 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):
......
......@@ -7,14 +7,15 @@ from pip._internal.exceptions import CommandError
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
from pip._internal.commands import (
commands_dict, create_command, get_similar_commands,
)
try:
# 'pip help' with no args is handled by pip.__init__.parseopt()
......@@ -31,7 +32,7 @@ class HelpCommand(Command):
raise CommandError(' - '.join(msg))
command = commands_dict[cmd_name]()
command = create_command(cmd_name)
command.parser.print_help()
return SUCCESS
......@@ -11,21 +11,22 @@ from pip._vendor import pkg_resources
from pip._internal.cache import WheelCache
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.cli.req_command import RequirementCommand
from pip._internal.cli.status_codes import ERROR
from pip._internal.exceptions import (
CommandError, InstallationError, PreviousBuildDirError,
CommandError,
InstallationError,
PreviousBuildDirError,
)
from pip._internal.legacy_resolve import Resolver
from pip._internal.locations import distutils_scheme
from pip._internal.operations.check import check_install_conflicts
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req import RequirementSet, install_given_reqs
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, get_installed_version,
ensure_dir,
get_installed_version,
protect_pip_from_modification_on_windows,
)
from pip._internal.utils.temp_dir import TempDirectory
......@@ -84,7 +85,6 @@ class InstallCommand(RequirementCommand):
pip also supports installing from "requirements files," which provide
an easy way to specify a whole environment to be installed.
"""
name = 'install'
usage = """
%prog [options] <requirement specifier> [package-index-options] ...
......@@ -93,8 +93,6 @@ class InstallCommand(RequirementCommand):
%prog [options] [-e] <local project path> ...
%prog [options] <archive url/path> ..."""
summary = 'Install packages.'
def __init__(self, *args, **kw):
super(InstallCommand, self).__init__(*args, **kw)
......@@ -184,7 +182,11 @@ class InstallCommand(RequirementCommand):
'-I', '--ignore-installed',
dest='ignore_installed',
action='store_true',
help='Ignore the installed packages (reinstalling instead).')
help='Ignore the installed packages, overwriting them. '
'This can break your system if the existing package '
'is of a different version or was installed '
'with a different package manager!'
)
cmd_opts.add_option(cmdoptions.ignore_requires_python())
cmd_opts.add_option(cmdoptions.no_build_isolation())
......@@ -316,31 +318,25 @@ class InstallCommand(RequirementCommand):
try:
self.populate_requirement_set(
requirement_set, args, options, finder, session,
self.name, wheel_cache
wheel_cache
)
preparer = RequirementPreparer(
build_dir=directory.path,
src_dir=options.src_dir,
download_dir=None,
wheel_download_dir=None,
progress_bar=options.progress_bar,
build_isolation=options.build_isolation,
preparer = self.make_requirement_preparer(
temp_directory=directory,
options=options,
req_tracker=req_tracker,
)
resolver = Resolver(
resolver = self.make_resolver(
preparer=preparer,
finder=finder,
session=session,
options=options,
wheel_cache=wheel_cache,
use_user_site=options.use_user_site,
upgrade_strategy=upgrade_strategy,
force_reinstall=options.force_reinstall,
ignore_dependencies=options.ignore_dependencies,
ignore_requires_python=options.ignore_requires_python,
ignore_installed=options.ignore_installed,
isolated=options.isolated_mode,
use_pep517=options.use_pep517
ignore_requires_python=options.ignore_requires_python,
force_reinstall=options.force_reinstall,
upgrade_strategy=upgrade_strategy,
use_pep517=options.use_pep517,
)
resolver.resolve(requirement_set)
......
......@@ -7,29 +7,29 @@ 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.exceptions import CommandError
from pip._internal.index 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,
)
from pip._internal.utils.packaging import get_installer
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'
usage = """
%prog [options]"""
summary = 'List installed packages.'
def __init__(self, *args, **kw):
super(ListCommand, self).__init__(*args, **kw)
......
......@@ -12,6 +12,7 @@ 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
......@@ -22,12 +23,11 @@ from pip._internal.utils.logging import indent_log
logger = logging.getLogger(__name__)
class SearchCommand(Command):
class SearchCommand(SessionCommandMixin, Command):
"""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):
......
......@@ -19,10 +19,9 @@ 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):
......
......@@ -3,13 +3,14 @@ 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.exceptions import InstallationError
from pip._internal.req import parse_requirements
from pip._internal.req.constructors import install_req_from_line
from pip._internal.utils.misc import protect_pip_from_modification_on_windows
class UninstallCommand(Command):
class UninstallCommand(SessionCommandMixin, Command):
"""
Uninstall packages.
......@@ -19,11 +20,10 @@ 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)
......
......@@ -6,10 +6,8 @@ import os
from pip._internal.cache import WheelCache
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import RequirementCommand
from pip._internal.cli.req_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.utils.temp_dir import TempDirectory
......@@ -33,7 +31,6 @@ class WheelCommand(RequirementCommand):
"""
name = 'wheel'
usage = """
%prog [options] <requirement specifier> ...
%prog [options] -r <requirements file> ...
......@@ -41,8 +38,6 @@ class WheelCommand(RequirementCommand):
%prog [options] [-e] <local project path> ...
%prog [options] <archive url/path> ..."""
summary = 'Build wheels from your requirements.'
def __init__(self, *args, **kw):
super(WheelCommand, self).__init__(*args, **kw)
......@@ -129,32 +124,24 @@ class WheelCommand(RequirementCommand):
try:
self.populate_requirement_set(
requirement_set, args, options, finder, session,
self.name, wheel_cache
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,
preparer = self.make_requirement_preparer(
temp_directory=directory,
options=options,
req_tracker=req_tracker,
wheel_download_dir=options.wheel_dir,
)
resolver = Resolver(
resolver = self.make_resolver(
preparer=preparer,
finder=finder,
session=session,
options=options,
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
use_pep517=options.use_pep517,
)
resolver.resolve(requirement_set)
......
......@@ -11,6 +11,9 @@ Some terminology:
A single word describing where the configuration key-value pair came from
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import locale
import logging
import os
......@@ -19,7 +22,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
......
......@@ -10,6 +10,7 @@ import platform
import re
import shutil
import sys
from contextlib import contextmanager
from pip._vendor import requests, urllib3
from pip._vendor.cachecontrol import CacheControlAdapter
......@@ -36,10 +37,22 @@ from pip._internal.utils.filesystem import check_path_owner
from pip._internal.utils.glibc import libc_ver
from pip._internal.utils.marker_files import write_delete_marker_file
from pip._internal.utils.misc import (
ARCHIVE_EXTENSIONS, ask, ask_input, ask_password, ask_path_exists,
backup_dir, consume, display_path, format_size, get_installed_version,
path_to_url, remove_auth_from_url, rmtree, split_auth_netloc_from_url,
splitext, unpack_file,
ARCHIVE_EXTENSIONS,
ask,
ask_input,
ask_password,
ask_path_exists,
backup_dir,
consume,
display_path,
format_size,
get_installed_version,
path_to_url,
remove_auth_from_url,
rmtree,
split_auth_netloc_from_url,
splitext,
unpack_file,
)
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
......@@ -470,70 +483,38 @@ class LocalFSAdapter(BaseAdapter):
pass
@contextmanager
def suppressed_cache_errors():
"""If we can't access the cache then we can just skip caching and process
requests as if caching wasn't enabled.
"""
try:
yield
except (LockError, OSError, IOError):
pass
class SafeFileCache(FileCache):
"""
A file based cache which is safe to use even when the target directory may
not be accessible or writable.
"""
def __init__(self, *args, **kwargs):
super(SafeFileCache, self).__init__(*args, **kwargs)
# Check to ensure that the directory containing our cache directory
# is owned by the user current executing pip. If it does not exist
# we will check the parent directory until we find one that does exist.
# If it is not owned by the user executing pip then we will disable
# the cache and log a warning.
if not check_path_owner(self.directory):
logger.warning(
"The directory '%s' or its parent directory is not owned by "
"the current user and the cache has been disabled. Please "
"check the permissions and owner of that directory. If "
"executing pip with sudo, you may want sudo's -H flag.",
self.directory,
)
# Set our directory to None to disable the Cache
self.directory = None
def __init__(self, directory, *args, **kwargs):
assert directory is not None, "Cache directory must not be None."
super(SafeFileCache, self).__init__(directory, *args, **kwargs)
def get(self, *args, **kwargs):
# If we don't have a directory, then the cache should be a no-op.
if self.directory is None:
return
try:
with suppressed_cache_errors():
return super(SafeFileCache, self).get(*args, **kwargs)
except (LockError, OSError, IOError):
# We intentionally silence this error, if we can't access the cache
# then we can just skip caching and process the request as if
# caching wasn't enabled.
pass
def set(self, *args, **kwargs):
# If we don't have a directory, then the cache should be a no-op.
if self.directory is None:
return
try:
with suppressed_cache_errors():
return super(SafeFileCache, self).set(*args, **kwargs)
except (LockError, OSError, IOError):
# We intentionally silence this error, if we can't access the cache
# then we can just skip caching and process the request as if
# caching wasn't enabled.
pass
def delete(self, *args, **kwargs):
# If we don't have a directory, then the cache should be a no-op.
if self.directory is None:
return
try:
with suppressed_cache_errors():
return super(SafeFileCache, self).delete(*args, **kwargs)
except (LockError, OSError, IOError):
# We intentionally silence this error, if we can't access the cache
# then we can just skip caching and process the request as if
# caching wasn't enabled.
pass
class InsecureHTTPAdapter(HTTPAdapter):
......@@ -581,6 +562,19 @@ class PipSession(requests.Session):
backoff_factor=0.25,
)
# Check to ensure that the directory containing our cache directory
# is owned by the user current executing pip. If it does not exist
# we will check the parent directory until we find one that does exist.
if cache and not check_path_owner(cache):
logger.warning(
"The directory '%s' or its parent directory is not owned by "
"the current user and the cache has been disabled. Please "
"check the permissions and owner of that directory. If "
"executing pip with sudo, you may want sudo's -H flag.",
cache,
)
cache = None
# We want to _only_ cache responses on securely fetched origins. We do
# this because we can't validate the response of an insecurely fetched
# origin, and we don't want someone to be able to poison the cache and
......@@ -638,29 +632,30 @@ def get_file_content(url, comes_from=None, session=None):
"get_file_content() missing 1 required keyword argument: 'session'"
)
match = _scheme_re.search(url)
if match:
scheme = match.group(1).lower()
if (scheme == 'file' and comes_from and
comes_from.startswith('http')):
scheme = _get_url_scheme(url)
if scheme in ['http', 'https']:
# FIXME: catch some errors
resp = session.get(url)
resp.raise_for_status()
return resp.url, resp.text
elif scheme == 'file':
if comes_from and comes_from.startswith('http'):
raise InstallationError(
'Requirements file %s references URL %s, which is local'
% (comes_from, url))
if scheme == 'file':
path = url.split(':', 1)[1]
path = path.replace('\\', '/')
match = _url_slash_drive_re.match(path)
if match:
path = match.group(1) + ':' + path.split('|', 1)[1]
path = urllib_parse.unquote(path)
if path.startswith('/'):
path = '/' + path.lstrip('/')
url = path
else:
# FIXME: catch some errors
resp = session.get(url)
resp.raise_for_status()
return resp.url, resp.text
path = url.split(':', 1)[1]
path = path.replace('\\', '/')
match = _url_slash_drive_re.match(path)
if match:
path = match.group(1) + ':' + path.split('|', 1)[1]
path = urllib_parse.unquote(path)
if path.startswith('/'):
path = '/' + path.lstrip('/')
url = path
try:
with open(url, 'rb') as f:
content = auto_decode(f.read())
......@@ -671,16 +666,22 @@ def get_file_content(url, comes_from=None, session=None):
return url, content
_scheme_re = re.compile(r'^(http|https|file):', re.I)
_url_slash_drive_re = re.compile(r'/*([a-z])\|', re.I)
def _get_url_scheme(url):
# type: (Union[str, Text]) -> Optional[Text]
if ':' not in url:
return None
return url.split(':', 1)[0].lower()
def is_url(name):
# type: (Union[str, Text]) -> bool
"""Returns true if the name looks like a URL"""
if ':' not in name:
scheme = _get_url_scheme(name)
if scheme is None:
return False
scheme = name.split(':', 1)[0].lower()
return scheme in ['http', 'https', 'file', 'ftp'] + vcs.all_schemes
......@@ -948,12 +949,23 @@ def unpack_file_url(
of the link file inside download_dir.
"""
link_path = url_to_path(link.url_without_fragment)
# If it's a url to a local directory
if is_dir_url(link):
def ignore(d, names):
# 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
return ['.tox', '.nox'] if d == link_path else []
if os.path.isdir(location):
rmtree(location)
shutil.copytree(link_path, location, symlinks=True)
shutil.copytree(link_path,
location,
symlinks=True,
ignore=ignore)
if download_dir:
logger.info('Link is a directory, ignoring download_dir')
return
......
"""Routines related to PyPI, indexes"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import cgi
......@@ -19,7 +23,9 @@ from pip._vendor.six.moves.urllib import request as urllib_request
from pip._internal.download import is_url, url_to_path
from pip._internal.exceptions import (
BestVersionAlreadyInstalled, DistributionNotFound, InvalidWheelFilename,
BestVersionAlreadyInstalled,
DistributionNotFound,
InvalidWheelFilename,
UnsupportedWheel,
)
from pip._internal.models.candidate import InstallationCandidate
......@@ -30,7 +36,10 @@ from pip._internal.models.target_python import TargetPython
from pip._internal.utils.compat import ipaddress
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import (
ARCHIVE_EXTENSIONS, SUPPORTED_EXTENSIONS, WHEEL_EXTENSION, path_to_url,
ARCHIVE_EXTENSIONS,
SUPPORTED_EXTENSIONS,
WHEEL_EXTENSION,
path_to_url,
redact_password_from_url,
)
from pip._internal.utils.packaging import check_requires_python
......
......@@ -10,6 +10,9 @@ for sub-dependencies
a. "first found, wins" (where the order is breadth first)
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import logging
import sys
from collections import defaultdict
......@@ -18,16 +21,22 @@ from itertools import chain
from pip._vendor.packaging import specifiers
from pip._internal.exceptions import (
BestVersionAlreadyInstalled, DistributionNotFound, HashError, HashErrors,
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,
dist_in_usersite,
ensure_dir,
normalize_version_info,
)
from pip._internal.utils.packaging import (
check_requires_python, get_requires_python,
check_requires_python,
get_requires_python,
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
......@@ -302,9 +311,11 @@ class Resolver(object):
)
upgrade_allowed = self._is_upgrade_allowed(req)
# We eagerly populate the link, since that's our "legacy" behavior.
req.populate_link(self.finder, upgrade_allowed, self.require_hashes)
abstract_dist = self.preparer.prepare_linked_requirement(
req, self.session, self.finder, upgrade_allowed,
self.require_hashes
req, self.session, self.finder, self.require_hashes
)
# NOTE
......
"""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
......
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
......
......@@ -4,8 +4,11 @@ import re
from pip._vendor.six.moves.urllib import parse as urllib_parse
from pip._internal.utils.misc import (
WHEEL_EXTENSION, path_to_url, redact_password_from_url,
split_auth_from_netloc, splitext,
WHEEL_EXTENSION,
path_to_url,
redact_password_from_url,
split_auth_from_netloc,
splitext,
)
from pip._internal.utils.models import KeyBasedCompareMixin
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
......
"""Validation of dependencies of packages
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import logging
from collections import namedtuple
......
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import collections
......@@ -11,11 +14,13 @@ 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.misc import (
dist_is_editable, get_installed_distributions,
dist_is_editable,
get_installed_distributions,
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
......
"""Prepares a distribution for installation
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import logging
import os
......@@ -11,11 +14,18 @@ from pip._internal.distributions import (
)
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,
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,
HashUnpinned,
InstallationError,
PreviousBuildDirError,
VcsHashUnsupported,
)
from pip._internal.utils.compat import expanduser
from pip._internal.utils.hashes import MissingHashes
......@@ -35,6 +45,15 @@ if MYPY_CHECK_RUNNING:
logger = logging.getLogger(__name__)
def _get_prepared_distribution(req, req_tracker, finder, build_isolation):
"""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
class RequirementPreparer(object):
"""Prepares a Requirement
"""
......@@ -97,18 +116,20 @@ class RequirementPreparer(object):
req, # type: InstallRequirement
session, # type: PipSession
finder, # type: PackageFinder
upgrade_allowed, # type: bool
require_hashes # type: bool
require_hashes, # type: bool
):
# type: (...) -> AbstractDistribution
"""Prepare a requirement that would be obtained from req.link
"""
assert req.link
link = req.link
# TODO: Breakup into smaller functions
if req.link and req.link.scheme == 'file':
path = url_to_path(req.link.url)
if link.scheme == 'file':
path = url_to_path(link.url)
logger.info('Processing %s', display_path(path))
else:
logger.info('Collecting %s', req)
logger.info('Collecting %s', req.req or req)
with indent_log():
# @@ if filesystem packages are not marked
......@@ -131,17 +152,6 @@ class RequirementPreparer(object):
"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
......@@ -179,11 +189,11 @@ class RequirementPreparer(object):
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:
if 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 link.is_wheel:
if download_dir:
# When downloading, we only unpack wheels to get
# metadata.
......@@ -193,7 +203,7 @@ class RequirementPreparer(object):
# wheel.
autodelete_unpacked = False
unpack_url(
req.link, req.source_dir,
link, req.source_dir,
download_dir, autodelete_unpacked,
session=session, hashes=hashes,
progress_bar=self.progress_bar
......@@ -207,16 +217,16 @@ class RequirementPreparer(object):
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,
(req, exc, link)
)
abstract_dist = _get_prepared_distribution(
req, self.req_tracker, finder, self.build_isolation,
)
if self._download_should_save:
# Make a .zip of the source_dir we already created.
if not req.link.is_artifact:
if not link.is_artifact:
req.archive(self.download_dir)
return abstract_dist
......@@ -244,11 +254,9 @@ class RequirementPreparer(object):
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, finder, self.build_isolation,
)
if self._download_should_save:
req.archive(self.download_dir)
......
......@@ -16,7 +16,7 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import (
Tuple, Callable, List, Optional, Union, Dict
Tuple, Callable, List, Optional, Union, Dict, Set
)
Pep425Tag = Tuple[str, str, str]
......@@ -105,6 +105,8 @@ def get_abi_tag():
(CPython 2, PyPy)."""
soabi = get_config_var('SOABI')
impl = get_abbr_impl()
abi = None # type: Optional[str]
if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'):
d = ''
m = ''
......@@ -129,8 +131,7 @@ def get_abi_tag():
abi = 'cp' + soabi.split('-')[1]
elif soabi:
abi = soabi.replace('.', '_').replace('-', '_')
else:
abi = None
return abi
......@@ -310,7 +311,7 @@ def get_supported(
if abi:
abis[0:0] = [abi]
abi3s = set()
abi3s = set() # type: Set[str]
for suffix in get_extension_suffixes():
if suffix.startswith('.abi'):
abi3s.add(suffix.split('.', 2)[1])
......
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import logging
......
......@@ -8,6 +8,9 @@ These are meant to be used elsewhere within pip to create instances of
InstallRequirement.
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import logging
import os
import re
......
......@@ -2,6 +2,9 @@
Requirements file parsing
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import optparse
......@@ -18,7 +21,8 @@ from pip._internal.download import get_file_content
from pip._internal.exceptions import RequirementsFileParseError
from pip._internal.models.search_scope import SearchScope
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.utils.typing import MYPY_CHECK_RUNNING
......
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import logging
......@@ -15,7 +18,7 @@ from pip._vendor.packaging.version import Version
from pip._vendor.packaging.version import parse as parse_version
from pip._vendor.pep517.wrappers import Pep517HookCaller
from pip._internal import wheel
from pip._internal import pep425tags, wheel
from pip._internal.build_env import NoOpBuildEnvironment
from pip._internal.exceptions import InstallationError
from pip._internal.models.link import Link
......@@ -26,9 +29,17 @@ from pip._internal.utils.hashes import Hashes
from pip._internal.utils.logging import indent_log
from pip._internal.utils.marker_files import PIP_DELETE_MARKER_FILENAME
from pip._internal.utils.misc import (
_make_build_dir, ask_path_exists, backup_dir, call_subprocess,
display_path, dist_in_site_packages, dist_in_usersite, ensure_dir,
get_installed_version, redact_password_from_url, rmtree,
_make_build_dir,
ask_path_exists,
backup_dir,
call_subprocess,
display_path,
dist_in_site_packages,
dist_in_usersite,
ensure_dir,
get_installed_version,
redact_password_from_url,
rmtree,
)
from pip._internal.utils.packaging import get_metadata
from pip._internal.utils.setuptools_build import make_setuptools_shim_args
......@@ -211,7 +222,12 @@ class InstallRequirement(object):
self.link = finder.find_requirement(self, upgrade)
if self._wheel_cache is not None and not require_hashes:
old_link = self.link
self.link = self._wheel_cache.get(self.link, self.name)
supported_tags = pep425tags.get_supported()
self.link = self._wheel_cache.get(
link=self.link,
package_name=self.name,
supported_tags=supported_tags,
)
if old_link != self.link:
logger.debug('Using cached wheel link: %s', self.link)
......@@ -608,9 +624,10 @@ class InstallRequirement(object):
'Running setup.py (path:%s) egg_info for package from %s',
self.setup_py_path, self.link,
)
base_cmd = make_setuptools_shim_args(self.setup_py_path)
if self.isolated:
base_cmd += ["--no-user-cfg"]
base_cmd = make_setuptools_shim_args(
self.setup_py_path,
no_user_config=self.isolated
)
egg_info_cmd = base_cmd + ['egg_info']
# We can't put the .egg-info files at the root, because then the
# source code will be mistaken for an installed egg, causing
......@@ -755,19 +772,19 @@ class InstallRequirement(object):
# type: (...) -> None
logger.info('Running setup.py develop for %s', self.name)
if self.isolated:
global_options = list(global_options) + ["--no-user-cfg"]
if prefix:
prefix_param = ['--prefix={}'.format(prefix)]
install_options = list(install_options) + prefix_param
base_cmd = make_setuptools_shim_args(
self.setup_py_path,
global_options=global_options,
no_user_config=self.isolated
)
with indent_log():
# FIXME: should we do --install-headers here too?
with self.build_env:
call_subprocess(
make_setuptools_shim_args(self.setup_py_path) +
list(global_options) +
base_cmd +
['develop', '--no-deps'] +
list(install_options),
......@@ -940,10 +957,6 @@ class InstallRequirement(object):
install_options = list(install_options) + \
self.options.get('install_options', [])
if self.isolated:
# https://github.com/python/mypy/issues/1174
global_options = global_options + ["--no-user-cfg"] # type: ignore
with TempDirectory(kind="record") as temp_dir:
record_filename = os.path.join(temp_dir.path, 'install-record.txt')
install_args = self.get_install_args(
......@@ -1010,10 +1023,13 @@ class InstallRequirement(object):
pycompile # type: bool
):
# type: (...) -> List[str]
install_args = make_setuptools_shim_args(self.setup_py_path,
unbuffered_output=True)
install_args += list(global_options) + \
['install', '--record', record_filename]
install_args = make_setuptools_shim_args(
self.setup_py_path,
global_options=global_options,
no_user_config=self.isolated,
unbuffered_output=True
)
install_args += ['install', '--record', record_filename]
install_args += ['--single-version-externally-managed']
if root is not None:
......
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import logging
from collections import OrderedDict
from pip._internal import pep425tags
from pip._internal.exceptions import InstallationError
from pip._internal.utils.logging import indent_log
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
......@@ -85,7 +89,8 @@ class RequirementSet(object):
# single requirements file.
if install_req.link and install_req.link.is_wheel:
wheel = Wheel(install_req.link.filename)
if self.check_supported_wheels and not wheel.supported():
tags = pep425tags.get_supported()
if (self.check_supported_wheels and not wheel.supported(tags)):
raise InstallationError(
"%s is not a supported wheel on this platform." %
wheel.filename
......
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import contextlib
......
......@@ -14,8 +14,15 @@ from pip._internal.locations import bin_py, bin_user
from pip._internal.utils.compat import WINDOWS, cache_from_source, uses_pycache
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import (
FakeFile, ask, dist_in_usersite, dist_is_local, egg_link_path, is_local,
normalize_path, renames, rmtree,
FakeFile,
ask,
dist_in_usersite,
dist_is_local,
egg_link_path,
is_local,
normalize_path,
renames,
rmtree,
)
from pip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
......@@ -593,6 +600,11 @@ class UninstallPthEntries(object):
# backslashes. This is correct for entries that describe absolute
# paths outside of site-packages, but all the others use forward
# slashes.
# os.path.splitdrive is used instead of os.path.isabs because isabs
# treats non-absolute paths with drive letter markings like c:foo\bar
# as absolute paths. It also does not recognize UNC paths if they don't
# have more than "\\sever\share". Valid examples: "\\server\share\" or
# "\\server\share\folder". Python 2.7.8+ support UNC in splitdrive.
if WINDOWS and not os.path.splitdrive(entry)[0]:
entry = entry.replace('\\', '/')
self.entries.add(entry)
......
......@@ -218,6 +218,8 @@ def _get_win_folder_from_registry(csidl_name):
def _get_win_folder_with_ctypes(csidl_name):
# type: (str) -> str
# On Python 2, ctypes.create_unicode_buffer().value returns "unicode",
# which isn't the same as str in the annotation above.
csidl_const = {
"CSIDL_APPDATA": 26,
"CSIDL_COMMON_APPDATA": 35,
......@@ -239,7 +241,8 @@ def _get_win_folder_with_ctypes(csidl_name):
if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
buf = buf2
return buf.value
# The type: ignore is explained under the type annotation for this function
return buf.value # type: ignore
if WINDOWS:
......
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
import codecs
import locale
import re
......
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import os
......
......@@ -5,7 +5,9 @@ import hashlib
from pip._vendor.six import iteritems, iterkeys, itervalues
from pip._internal.exceptions import (
HashMismatch, HashMissing, InstallationError,
HashMismatch,
HashMissing,
InstallationError,
)
from pip._internal.utils.misc import read_chunks
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
......
"""A helper module that injects SecureTransport, on import.
The import should be done as early as possible, to ensure all requests and
sessions (or whatever) are created after injecting SecureTransport.
Note that we only do the injection on macOS, when the linked OpenSSL is too
old to handle TLSv1.2.
"""
try:
import ssl
except ImportError:
pass
else:
import sys
# 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()
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import contextlib
import errno
import getpass
import io
# we have a submodule named 'logging' which would shadow this if we used the
# regular name:
import logging as std_logging
import logging
import os
import posixpath
import re
......@@ -32,12 +33,17 @@ from pip import __version__
from pip._internal.exceptions import CommandError, InstallationError
from pip._internal.locations import site_packages, user_site
from pip._internal.utils.compat import (
WINDOWS, console_to_str, expanduser, stdlib_pkgs, str_to_display,
WINDOWS,
console_to_str,
expanduser,
stdlib_pkgs,
str_to_display,
)
from pip._internal.utils.marker_files import write_delete_marker_file
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.virtualenv import (
running_under_virtualenv, virtualenv_no_global,
running_under_virtualenv,
virtualenv_no_global,
)
if PY2:
......@@ -75,8 +81,8 @@ __all__ = ['rmtree', 'display_path', 'backup_dir',
'get_installed_version', 'remove_auth_from_url']
logger = std_logging.getLogger(__name__)
subprocess_logger = std_logging.getLogger('pip.subprocessor')
logger = logging.getLogger(__name__)
subprocess_logger = logging.getLogger('pip.subprocessor')
LOG_DIVIDER = '----------------------------------------'
......@@ -851,12 +857,12 @@ def call_subprocess(
if show_stdout:
# Then log the subprocess output at INFO level.
log_subprocess = subprocess_logger.info
used_level = std_logging.INFO
used_level = logging.INFO
else:
# Then log the subprocess output using DEBUG. This also ensures
# it will be logged to the log file (aka user_log), if enabled.
log_subprocess = subprocess_logger.debug
used_level = std_logging.DEBUG
used_level = logging.DEBUG
# Whether the subprocess will be visible in the console.
showing_subprocess = subprocess_logger.getEffectiveLevel() <= used_level
......
......@@ -3,7 +3,7 @@ import sys
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
if MYPY_CHECK_RUNNING:
from typing import List
from typing import List, Sequence
# Shim to wrap setup.py invocation with setuptools
#
......@@ -20,12 +20,19 @@ _SETUPTOOLS_SHIM = (
)
def make_setuptools_shim_args(setup_py_path, unbuffered_output=False):
# type: (str, bool) -> List[str]
def make_setuptools_shim_args(
setup_py_path, # type: str
global_options=None, # type: Sequence[str]
no_user_config=False, # type: bool
unbuffered_output=False # type: bool
):
# type: (...) -> List[str]
"""
Get setuptools command arguments with shim wrapped setup file invocation.
:param setup_py_path: The path to setup.py to be wrapped.
:param global_options: Additional global options.
:param no_user_config: If True, disables personal user configuration.
:param unbuffered_output: If True, adds the unbuffered switch to the
argument list.
"""
......@@ -33,4 +40,8 @@ def make_setuptools_shim_args(setup_py_path, unbuffered_output=False):
if unbuffered_output:
args.append('-u')
args.extend(['-c', _SETUPTOOLS_SHIM.format(setup_py_path)])
if global_options:
args.extend(global_options)
if no_user_config:
args.append('--no-user-cfg')
return args
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import, division
import contextlib
......
......@@ -13,7 +13,9 @@ from pip._internal.utils.compat import samefile
from pip._internal.utils.misc import display_path, redact_password_from_url
from pip._internal.utils.temp_dir import TempDirectory
from pip._internal.vcs.versioncontrol import (
RemoteNotFoundError, VersionControl, vcs,
RemoteNotFoundError,
VersionControl,
vcs,
)
urlsplit = urllib_parse.urlsplit
......
......@@ -7,7 +7,9 @@ import sys
from pip._internal.utils.logging import indent_log
from pip._internal.utils.misc import (
display_path, rmtree, split_auth_from_netloc,
display_path,
rmtree,
split_auth_from_netloc,
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.vcs.versioncontrol import VersionControl, vcs
......
......@@ -12,7 +12,11 @@ from pip._vendor.six.moves.urllib import parse as urllib_parse
from pip._internal.exceptions import BadCommand
from pip._internal.utils.misc import (
ask_path_exists, backup_dir, call_subprocess, display_path, rmtree,
ask_path_exists,
backup_dir,
call_subprocess,
display_path,
rmtree,
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
......
"""
Support for installing and building the "wheel" binary package format.
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
from __future__ import absolute_import
import collections
......@@ -25,15 +29,22 @@ from pip._vendor.six import StringIO
from pip._internal import pep425tags
from pip._internal.download import unpack_url
from pip._internal.exceptions import (
InstallationError, InvalidWheelFilename, UnsupportedWheel,
InstallationError,
InvalidWheelFilename,
UnsupportedWheel,
)
from pip._internal.locations import distutils_scheme
from pip._internal.models.link import Link
from pip._internal.utils.logging import indent_log
from pip._internal.utils.marker_files import PIP_DELETE_MARKER_FILENAME
from pip._internal.utils.misc import (
LOG_DIVIDER, call_subprocess, captured_stdout, ensure_dir,
format_command_args, path_to_url, read_chunks,
LOG_DIVIDER,
call_subprocess,
captured_stdout,
ensure_dir,
format_command_args,
path_to_url,
read_chunks,
)
from pip._internal.utils.setuptools_build import make_setuptools_shim_args
from pip._internal.utils.temp_dir import TempDirectory
......@@ -725,25 +736,31 @@ class Wheel(object):
"""
return sorted(format_tag(tag) for tag in self.file_tags)
def support_index_min(self, tags=None):
# type: (Optional[List[Pep425Tag]]) -> Optional[int]
def support_index_min(self, tags):
# type: (List[Pep425Tag]) -> int
"""
Return the lowest index that one of the wheel's file_tag combinations
achieves in the supported_tags list e.g. if there are 8 supported tags,
and one of the file tags is first in the list, then return 0. Returns
None is the wheel is not supported.
achieves in the given list of supported tags.
For example, if there are 8 supported tags and one of the file tags
is first in the list, then return 0.
:param tags: the PEP 425 tags to check the wheel against, in order
with most preferred first.
:raises ValueError: If none of the wheel's file tags match one of
the supported tags.
"""
if tags is None: # for mock
tags = pep425tags.get_supported()
indexes = [tags.index(c) for c in self.file_tags if c in tags]
return min(indexes) if indexes else None
return min(tags.index(tag) for tag in self.file_tags if tag in tags)
def supported(self, tags=None):
# type: (Optional[List[Pep425Tag]]) -> bool
"""Is this wheel supported on this system?"""
if tags is None: # for mock
tags = pep425tags.get_supported()
return bool(set(tags).intersection(self.file_tags))
def supported(self, tags):
# type: (List[Pep425Tag]) -> bool
"""
Return whether the wheel is compatible with one of the given tags.
:param tags: the PEP 425 tags to check the wheel against.
"""
return not self.file_tags.isdisjoint(tags)
def _contains_egg_info(
......@@ -927,9 +944,11 @@ class WheelBuilder(object):
# isolating. Currently, it breaks Python in virtualenvs, because it
# relies on site.py to find parts of the standard library outside the
# virtualenv.
base_cmd = make_setuptools_shim_args(req.setup_py_path,
unbuffered_output=True)
return base_cmd + list(self.global_options)
return make_setuptools_shim_args(
req.setup_py_path,
global_options=self.global_options,
unbuffered_output=True
)
def _build_one_pep517(self, req, tempd, python_tag=None):
"""Build one InstallRequirement using the PEP 517 build process.
......
import invoke
from . import generate
from . import vendoring
from tools.automation import generate, vendoring
ns = invoke.Collection(generate, vendoring)
......@@ -9,7 +9,7 @@ _pip_completion()
{
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
COMP_CWORD=$COMP_CWORD \\
PIP_AUTO_COMPLETE=1 $1 ) )
PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) )
}
complete -o default -F _pip_completion pip"""),
('fish', """\
......@@ -29,7 +29,7 @@ function _pip_completion {
read -cn cword
reply=( $( COMP_WORDS="$words[*]" \\
COMP_CWORD=$(( cword-1 )) \\
PIP_AUTO_COMPLETE=1 $words[1] ) )
PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null ))
}
compctl -K _pip_completion pip"""),
)
......
......@@ -7,8 +7,12 @@ from doctest import ELLIPSIS, OutputChecker
import pytest
from tests.lib import (
_create_test_package, _create_test_package_with_srcdir, _git_commit,
need_bzr, need_mercurial, path_to_url,
_create_test_package,
_create_test_package_with_srcdir,
_git_commit,
need_bzr,
need_mercurial,
path_to_url,
)
distribute_re = re.compile('^distribute==[0-9.]+\n', re.MULTILINE)
......
......@@ -2,8 +2,7 @@ import pytest
from mock import Mock
from pip._internal.cli.base_command import ERROR, SUCCESS
from pip._internal.commands import commands_dict as commands
from pip._internal.commands.help import HelpCommand
from pip._internal.commands import commands_dict, create_command
from pip._internal.exceptions import CommandError
......@@ -13,7 +12,7 @@ def test_run_method_should_return_success_when_finds_command_name():
"""
options_mock = Mock()
args = ('freeze',)
help_cmd = HelpCommand()
help_cmd = create_command('help')
status = help_cmd.run(options_mock, args)
assert status == SUCCESS
......@@ -24,7 +23,7 @@ def test_run_method_should_return_success_when_command_name_not_specified():
"""
options_mock = Mock()
args = ()
help_cmd = HelpCommand()
help_cmd = create_command('help')
status = help_cmd.run(options_mock, args)
assert status == SUCCESS
......@@ -35,7 +34,7 @@ def test_run_method_should_raise_command_error_when_command_does_not_exist():
"""
options_mock = Mock()
args = ('mycommand',)
help_cmd = HelpCommand()
help_cmd = create_command('help')
with pytest.raises(CommandError):
help_cmd.run(options_mock, args)
......@@ -80,7 +79,7 @@ def test_help_commands_equally_functional(in_memory_pip):
assert sum(ret) == 0, 'exit codes of: ' + msg
assert all(len(o) > 0 for o in out)
for name, cls in commands.items():
for name in commands_dict:
assert (
in_memory_pip.pip('help', name).stdout ==
in_memory_pip.pip(name, '--help').stdout != ""
......
......@@ -12,9 +12,16 @@ from pip._internal.cli.status_codes import ERROR, SUCCESS
from pip._internal.models.index import PyPI, TestPyPI
from pip._internal.utils.misc import rmtree
from tests.lib import (
_create_svn_repo, _create_test_package, create_basic_wheel_for_package,
create_test_package_with_setup, need_bzr, need_mercurial, path_to_url,
pyversion, pyversion_tuple, requirements_file,
_create_svn_repo,
_create_test_package,
create_basic_wheel_for_package,
create_test_package_with_setup,
need_bzr,
need_mercurial,
path_to_url,
pyversion,
pyversion_tuple,
requirements_file,
)
from tests.lib.local_repos import local_checkout
from tests.lib.path import Path
......@@ -593,7 +600,7 @@ def test_install_global_option(script):
result = script.pip(
'install', '--global-option=--version', "INITools==0.1",
expect_stderr=True)
assert '0.1\n' in result.stdout
assert 'INITools==0.1\n' in result.stdout
def test_install_with_hacked_egg_info(script, data):
......@@ -1306,7 +1313,7 @@ def test_double_install_fail(script):
def _get_expected_error_text():
return (
"Package 'pkga' requires a different Python: {} not in '<1.0'"
).format(sys.version.split()[0])
).format('.'.join(map(str, sys.version_info[:3])))
def test_install_incompatible_python_requires(script):
......
......@@ -4,7 +4,9 @@ import textwrap
import pytest
from tests.lib import (
_create_test_package_with_subdirectory, path_to_url, pyversion,
_create_test_package_with_subdirectory,
path_to_url,
pyversion,
requirements_file,
)
from tests.lib.local_repos import local_checkout
......@@ -160,7 +162,7 @@ def test_respect_order_in_requirements_file(script, data):
)
downloaded = [line for line in result.stdout.split('\n')
if 'Collecting' in line]
if 'Processing' in line]
assert 'parent' in downloaded[0], (
'First download should be "parent" but was "%s"' % downloaded[0]
......
import pytest
from tests.lib import (
_change_test_package_version, _create_test_package, _test_path_to_file_url,
_change_test_package_version,
_create_test_package,
_test_path_to_file_url,
pyversion,
)
from tests.lib.git_submodule_helpers import (
_change_test_package_submodule, _create_test_package_with_submodule,
_change_test_package_submodule,
_create_test_package_with_submodule,
_pull_in_submodule_changes_to_module,
)
from tests.lib.local_repos import local_checkout
......
......@@ -99,9 +99,6 @@ def test_basic_install_from_wheel_file(script, data):
result.stdout)
# header installs are broke in pypy virtualenvs
# https://github.com/pypa/virtualenv/issues/510
@pytest.mark.skipif("hasattr(sys, 'pypy_version_info')")
def test_install_from_wheel_with_headers(script, data):
"""
Test installing from a wheel file with headers
......
......@@ -513,11 +513,10 @@ def test_list_path(tmpdir, script, data):
Test list with --path.
"""
result = script.pip('list', '--path', tmpdir, '--format=json')
assert {'name': 'simple',
'version': '2.0'} not in json.loads(result.stdout)
json_result = json.loads(result.stdout)
assert {'name': 'simple', 'version': '2.0'} not in json_result
script.pip('install', '--find-links', data.find_links,
'--target', tmpdir, 'simple==2.0')
script.pip_install_local('--target', tmpdir, 'simple==2.0')
result = script.pip('list', '--path', tmpdir, '--format=json')
json_result = json.loads(result.stdout)
assert {'name': 'simple', 'version': '2.0'} in json_result
......@@ -528,10 +527,9 @@ def test_list_path_exclude_user(tmpdir, script, data):
Test list with --path and make sure packages from --user are not picked
up.
"""
script.pip_install_local('--find-links', data.find_links,
'--user', 'simple2')
script.pip('install', '--find-links', data.find_links,
'--target', tmpdir, 'simple==1.0')
script.pip_install_local('--user', 'simple2')
script.pip_install_local('--target', tmpdir, 'simple==1.0')
result = script.pip('list', '--user', '--format=json')
json_result = json.loads(result.stdout)
assert {'name': 'simple2', 'version': '3.0'} in json_result
......@@ -549,10 +547,10 @@ def test_list_path_multiple(tmpdir, script, data):
os.mkdir(path1)
path2 = tmpdir / "path2"
os.mkdir(path2)
script.pip('install', '--find-links', data.find_links,
'--target', path1, 'simple==2.0')
script.pip('install', '--find-links', data.find_links,
'--target', path2, 'simple2==3.0')
script.pip_install_local('--target', path1, 'simple==2.0')
script.pip_install_local('--target', path2, 'simple2==3.0')
result = script.pip('list', '--path', path1, '--format=json')
json_result = json.loads(result.stdout)
assert {'name': 'simple', 'version': '2.0'} in json_result
......
import pytest
from pip._vendor import pytoml
from pip._internal.build_env import BuildEnvironment
......@@ -198,6 +199,7 @@ def test_explicit_setuptools_backend(script, tmpdir, data, common_wheels):
result.assert_installed(name, editable=False)
@pytest.mark.network
def test_pep517_and_build_options(script, tmpdir, data, common_wheels):
"""Backend generated requirements are installed in the build env"""
project_dir, name = make_pyproject_with_setup(tmpdir)
......
......@@ -4,8 +4,11 @@ import pretend
import pytest
from pip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS
from pip._internal.commands import create_command
from pip._internal.commands.search import (
SearchCommand, highest_version, print_results, transform_hits,
highest_version,
print_results,
transform_hits,
)
from tests.lib import pyversion
......@@ -108,7 +111,7 @@ def test_run_method_should_return_success_when_find_packages():
"""
Test SearchCommand.run for found package
"""
command = SearchCommand()
command = create_command('search')
cmdline = "--index=https://pypi.org/pypi pip"
options, args = command.parse_args(cmdline.split())
status = command.run(options, args)
......@@ -120,7 +123,7 @@ def test_run_method_should_return_no_matches_found_when_does_not_find_pkgs():
"""
Test SearchCommand.run for no matches
"""
command = SearchCommand()
command = create_command('search')
cmdline = "--index=https://pypi.org/pypi nonexistentpackage"
options, args = command.parse_args(cmdline.split())
status = command.run(options, args)
......
......@@ -8,7 +8,11 @@ import pytest
from pip._internal.vcs.bazaar import Bazaar
from tests.lib import (
_test_path_to_file_url, _vcs_add, create_file, is_bzr_installed, need_bzr,
_test_path_to_file_url,
_vcs_add,
create_file,
is_bzr_installed,
need_bzr,
)
......
......@@ -5,13 +5,11 @@ import os
from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
from pip._internal.commands import commands_dict
from pip._internal.commands import CommandInfo, commands_dict
from tests.lib.configuration_helpers import reset_os_environ
class FakeCommand(Command):
name = 'fake'
summary = name
def main(self, args):
index_opts = cmdoptions.make_option_group(
......@@ -26,8 +24,10 @@ class AddFakeCommandMixin(object):
def setup(self):
self.environ_before = os.environ.copy()
commands_dict[FakeCommand.name] = FakeCommand
commands_dict['fake'] = CommandInfo(
'tests.lib.options_helpers', 'FakeCommand', 'fake summary',
)
def teardown(self):
reset_os_environ(self.environ_before)
commands_dict.pop(FakeCommand.name)
commands_dict.pop('fake')
......@@ -2,13 +2,15 @@ import logging
import os
import time
from mock import patch
from pip._internal.cli.base_command import Command
from pip._internal.utils.logging import BrokenStdoutLoggingError
class FakeCommand(Command):
name = 'fake'
summary = name
_name = 'fake'
def __init__(self, run_func=None, error=False):
if error:
......@@ -16,7 +18,7 @@ class FakeCommand(Command):
raise SystemExit(1)
self.run_func = run_func
super(FakeCommand, self).__init__()
super(FakeCommand, self).__init__(self._name, self._name)
def main(self, args):
args.append("--disable-pip-version-check")
......@@ -29,8 +31,7 @@ class FakeCommand(Command):
class FakeCommandWithUnicode(FakeCommand):
name = 'fake_unicode'
summary = name
_name = 'fake_unicode'
def run(self, options, args):
logging.getLogger("pip.tests").info(b"bytes here \xE9")
......@@ -73,6 +74,16 @@ class TestCommand(object):
assert 'Traceback (most recent call last):' in stderr
@patch('pip._internal.cli.req_command.Command.handle_pip_version_check')
def test_handle_pip_version_check_called(mock_handle_version_check):
"""
Check that Command.handle_pip_version_check() is called.
"""
cmd = FakeCommand()
cmd.main([])
mock_handle_version_check.assert_called_once()
class Test_base_command_logging(object):
"""
Test `pip.base_command.Command` setting up logging consumers based on
......
......@@ -5,7 +5,8 @@ import pytest
from mock import patch
from pip._internal.cli.cmdoptions import (
_convert_python_version, make_search_scope,
_convert_python_version,
make_search_scope,
)
......
import pytest
from mock import patch
from pip._internal.cli.req_command import (
IndexGroupCommand,
RequirementCommand,
SessionCommandMixin,
)
from pip._internal.commands import commands_dict, create_command
# These are the expected names of the commands whose classes inherit from
# IndexGroupCommand.
EXPECTED_INDEX_GROUP_COMMANDS = ['download', 'install', 'list', 'wheel']
def check_commands(pred, expected):
"""
Check the commands satisfying a predicate.
"""
commands = [create_command(name) for name in sorted(commands_dict)]
actual = [command.name for command in commands if pred(command)]
assert actual == expected, 'actual: {}'.format(actual)
def test_commands_dict__order():
"""
Check the ordering of commands_dict.
"""
names = list(commands_dict)
# A spot-check is sufficient to check that commands_dict encodes an
# ordering.
assert names[0] == 'install'
assert names[-1] == 'help'
@pytest.mark.parametrize('name', list(commands_dict))
def test_create_command(name):
"""Test creating an instance of each available command."""
command = create_command(name)
assert command.name == name
assert command.summary == commands_dict[name].summary
def test_session_commands():
"""
Test which commands inherit from SessionCommandMixin.
"""
def is_session_command(command):
return isinstance(command, SessionCommandMixin)
expected = ['download', 'install', 'list', 'search', 'uninstall', 'wheel']
check_commands(is_session_command, expected)
def test_index_group_commands():
"""
Test the commands inheriting from IndexGroupCommand.
"""
def is_index_group_command(command):
return isinstance(command, IndexGroupCommand)
check_commands(is_index_group_command, EXPECTED_INDEX_GROUP_COMMANDS)
# Also check that the commands inheriting from IndexGroupCommand are
# exactly the commands with the --no-index option.
def has_option_no_index(command):
return command.parser.has_option('--no-index')
check_commands(has_option_no_index, EXPECTED_INDEX_GROUP_COMMANDS)
@pytest.mark.parametrize('command_name', EXPECTED_INDEX_GROUP_COMMANDS)
@pytest.mark.parametrize(
'disable_pip_version_check, no_index, expected_called',
[
# pip_version_check() is only called when both
# disable_pip_version_check and no_index are False.
(False, False, True),
(False, True, False),
(True, False, False),
(True, True, False),
],
)
@patch('pip._internal.cli.req_command.pip_version_check')
def test_index_group_handle_pip_version_check(
mock_version_check, command_name, disable_pip_version_check, no_index,
expected_called,
):
"""
Test whether pip_version_check() is called when handle_pip_version_check()
is called, for each of the IndexGroupCommand classes.
"""
command = create_command(command_name)
options = command.parser.get_default_values()
options.disable_pip_version_check = disable_pip_version_check
options.no_index = no_index
command.handle_pip_version_check(options)
if expected_called:
mock_version_check.assert_called_once()
else:
mock_version_check.assert_not_called()
def test_requirement_commands():
"""
Test which commands inherit from RequirementCommand.
"""
def is_requirement_command(command):
return isinstance(command, RequirementCommand)
check_commands(is_requirement_command, ['download', 'install', 'wheel'])
......@@ -7,7 +7,11 @@ import pytest
import pip._internal.utils.compat as pip_compat
from pip._internal.utils.compat import (
console_to_str, expanduser, get_path_uid, native_str, str_to_display,
console_to_str,
expanduser,
get_path_uid,
native_str,
str_to_display,
)
......
......@@ -11,9 +11,17 @@ from mock import Mock, patch
import pip
from pip._internal.download import (
CI_ENVIRONMENT_VARIABLES, MultiDomainBasicAuth, PipSession, SafeFileCache,
_download_http_url, parse_content_disposition, sanitize_content_filename,
unpack_file_url, unpack_http_url, url_to_path,
CI_ENVIRONMENT_VARIABLES,
MultiDomainBasicAuth,
PipSession,
SafeFileCache,
_download_http_url,
_get_url_scheme,
parse_content_disposition,
sanitize_content_filename,
unpack_file_url,
unpack_http_url,
url_to_path,
)
from pip._internal.exceptions import HashMismatch
from pip._internal.models.link import Link
......@@ -284,6 +292,16 @@ def test_download_http_url__no_directory_traversal(tmpdir):
assert actual == ['out_dir_file']
@pytest.mark.parametrize("url,expected", [
('http://localhost:8080/', 'http'),
('file:c:/path/to/file', 'file'),
('file:/dev/null', 'file'),
('', None),
])
def test__get_url_scheme(url, expected):
assert _get_url_scheme(url) == expected
@pytest.mark.parametrize("url,win_expected,non_win_expected", [
('file:tmp', 'tmp', 'tmp'),
('file:c:/path/to/file', r'C:\path\to\file', 'c:/path/to/file'),
......@@ -413,6 +431,41 @@ class Test_unpack_file_url(object):
assert os.path.isdir(os.path.join(self.build_dir, 'fspkg'))
@pytest.mark.parametrize('exclude_dir', [
'.nox',
'.tox'
])
def test_unpack_file_url_excludes_expected_dirs(tmpdir, exclude_dir):
src_dir = tmpdir / 'src'
dst_dir = tmpdir / 'dst'
src_included_file = src_dir.joinpath('file.txt')
src_excluded_dir = src_dir.joinpath(exclude_dir)
src_excluded_file = src_dir.joinpath(exclude_dir, 'file.txt')
src_included_dir = src_dir.joinpath('subdir', exclude_dir)
# set up source directory
src_excluded_dir.mkdir(parents=True)
src_included_dir.mkdir(parents=True)
src_included_file.touch()
src_excluded_file.touch()
dst_included_file = dst_dir.joinpath('file.txt')
dst_excluded_dir = dst_dir.joinpath(exclude_dir)
dst_excluded_file = dst_dir.joinpath(exclude_dir, 'file.txt')
dst_included_dir = dst_dir.joinpath('subdir', exclude_dir)
src_link = Link(path_to_url(src_dir))
unpack_file_url(
src_link,
dst_dir,
download_dir=None
)
assert not os.path.isdir(dst_excluded_dir)
assert not os.path.isfile(dst_excluded_file)
assert os.path.isfile(dst_included_file)
assert os.path.isdir(dst_included_dir)
class TestSafeFileCache:
"""
The no_perms test are useless on Windows since SafeFileCache uses
......
......@@ -9,10 +9,14 @@ from pkg_resources import parse_version
import pip._internal.pep425tags
import pip._internal.wheel
from pip._internal.exceptions import (
BestVersionAlreadyInstalled, DistributionNotFound,
BestVersionAlreadyInstalled,
DistributionNotFound,
)
from pip._internal.index import (
CandidateEvaluator, InstallationCandidate, Link, LinkEvaluator,
CandidateEvaluator,
InstallationCandidate,
Link,
LinkEvaluator,
)
from pip._internal.models.target_python import TargetPython
from pip._internal.req.constructors import install_req_from_line
......
......@@ -6,11 +6,9 @@ from pip._internal.models.format_control import FormatControl
class SimpleCommand(Command):
name = 'fake'
summary = name
def __init__(self):
super(SimpleCommand, self).__init__()
super(SimpleCommand, self).__init__('fake', 'fake summary')
self.cmd_opts.add_option(cmdoptions.no_binary())
self.cmd_opts.add_option(cmdoptions.only_binary())
......
......@@ -8,10 +8,20 @@ from pip._vendor.packaging.specifiers import SpecifierSet
from pip._internal.download import PipSession
from pip._internal.index import (
CandidateEvaluator, CandidatePreferences, FormatControl, HTMLPage, Link,
LinkEvaluator, PackageFinder, _check_link_requires_python, _clean_link,
_determine_base_url, _extract_version_from_fragment,
_find_name_version_sep, _get_html_page, filter_unallowed_hashes,
CandidateEvaluator,
CandidatePreferences,
FormatControl,
HTMLPage,
Link,
LinkEvaluator,
PackageFinder,
_check_link_requires_python,
_clean_link,
_determine_base_url,
_extract_version_from_fragment,
_find_name_version_sep,
_get_html_page,
filter_unallowed_hashes,
)
from pip._internal.models.candidate import InstallationCandidate
from pip._internal.models.search_scope import SearchScope
......
......@@ -6,7 +6,11 @@ from pip._vendor.six.moves.urllib import request as urllib_request
from pip._internal.download import PipSession
from pip._internal.index import (
Link, _get_html_page, _get_html_response, _NotHTML, _NotHTTP,
Link,
_get_html_page,
_get_html_response,
_NotHTML,
_NotHTTP,
)
......
......@@ -4,7 +4,8 @@ import pytest
from pip._vendor import pkg_resources
from pip._internal.exceptions import (
NoneMetadataError, UnsupportedPythonVersion,
NoneMetadataError,
UnsupportedPythonVersion,
)
from pip._internal.legacy_resolve import _check_dist_requires_python
from pip._internal.utils.packaging import get_requires_python
......
......@@ -8,7 +8,9 @@ from mock import patch
from pip._vendor.six import PY2
from pip._internal.utils.logging import (
BrokenStdoutLoggingError, ColorizedStreamHandler, IndentingFormatter,
BrokenStdoutLoggingError,
ColorizedStreamHandler,
IndentingFormatter,
)
from pip._internal.utils.misc import captured_stderr, captured_stdout
......
......@@ -5,7 +5,7 @@ import pytest
import pip._internal.configuration
from pip._internal import main
from pip._internal.commands import ConfigurationCommand, DownloadCommand
from pip._internal.commands import create_command
from pip._internal.exceptions import PipError
from tests.lib.options_helpers import AddFakeCommandMixin
......@@ -193,7 +193,7 @@ class TestUsePEP517Options(object):
def parse_args(self, args):
# We use DownloadCommand since that is one of the few Command
# classes with the use_pep517 options.
command = DownloadCommand()
command = create_command('download')
options, args = command.parse_args(args)
return options
......@@ -411,7 +411,7 @@ class TestOptionsConfigFiles(object):
)
)
def test_config_file_options(self, monkeypatch, args, expect):
cmd = ConfigurationCommand()
cmd = create_command('config')
# Replace a handler with a no-op to avoid side effects
monkeypatch.setattr(cmd, "get_name", lambda *a: None)
......@@ -423,7 +423,7 @@ class TestOptionsConfigFiles(object):
assert expect == cmd._determine_file(options, need_value=False)
def test_config_file_venv_option(self, monkeypatch):
cmd = ConfigurationCommand()
cmd = create_command('config')
# Replace a handler with a no-op to avoid side effects
monkeypatch.setattr(cmd, "get_name", lambda *a: None)
......
......@@ -9,22 +9,30 @@ from pip._vendor import pkg_resources
from pip._vendor.packaging.markers import Marker
from pip._vendor.packaging.requirements import Requirement
from pip._internal.commands.install import InstallCommand
from pip._internal.commands import create_command
from pip._internal.download import PipSession
from pip._internal.exceptions import (
HashErrors, InstallationError, InvalidWheelFilename, PreviousBuildDirError,
HashErrors,
InstallationError,
InvalidWheelFilename,
PreviousBuildDirError,
)
from pip._internal.legacy_resolve import Resolver
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req import InstallRequirement, RequirementSet
from pip._internal.req.constructors import (
install_req_from_editable, install_req_from_line, parse_editable,
install_req_from_editable,
install_req_from_line,
parse_editable,
)
from pip._internal.req.req_file import process_line
from pip._internal.req.req_tracker import RequirementTracker
from pip._internal.utils.misc import path_to_url
from tests.lib import (
DATA_DIR, assert_raises_regexp, make_test_finder, requirements_file,
DATA_DIR,
assert_raises_regexp,
make_test_finder,
requirements_file,
)
......@@ -178,12 +186,11 @@ class TestRequirementSet(object):
req_set = RequirementSet(require_hashes=False)
finder = make_test_finder(find_links=[data.find_links])
session = finder.session
command = InstallCommand()
command = create_command('install')
with requirements_file('--require-hashes', tmpdir) as reqs_file:
options, args = command.parse_args(['-r', reqs_file])
command.populate_requirement_set(
req_set, args, options, finder, session, command.name,
wheel_cache=None,
req_set, args, options, finder, session, wheel_cache=None,
)
assert req_set.require_hashes
......
......@@ -10,15 +10,22 @@ from pretend import stub
import pip._internal.index
from pip._internal.download import PipSession
from pip._internal.exceptions import (
InstallationError, RequirementsFileParseError,
InstallationError,
RequirementsFileParseError,
)
from pip._internal.models.format_control import FormatControl
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 (
break_args_options, ignore_comments, join_lines, parse_requirements,
preprocess, process_line, skip_regex,
break_args_options,
ignore_comments,
join_lines,
parse_requirements,
preprocess,
process_line,
skip_regex,
)
from tests.lib import make_test_finder, requirements_file
......
......@@ -6,7 +6,8 @@ from pip._vendor.packaging.requirements import Requirement
from pip._internal.exceptions import InstallationError
from pip._internal.req.constructors import (
install_req_from_line, install_req_from_req_string,
install_req_from_line,
install_req_from_req_string,
)
from pip._internal.req.req_install import InstallRequirement
......
import os
import sys
import pytest
from mock import Mock
import pip._internal.req.req_uninstall
from pip._internal.req.req_uninstall import (
StashedUninstallPathSet, UninstallPathSet, compact,
compress_for_output_listing, compress_for_rename, uninstallation_paths,
StashedUninstallPathSet,
UninstallPathSet,
UninstallPthEntries,
compact,
compress_for_output_listing,
compress_for_rename,
uninstallation_paths,
)
from tests.lib import create_file
......@@ -129,6 +135,38 @@ class TestUninstallPathSet(object):
ups.add(file_nonexistent)
assert ups.paths == {file_extant}
def test_add_pth(self, tmpdir, monkeypatch):
monkeypatch.setattr(pip._internal.req.req_uninstall, 'is_local',
mock_is_local)
# Fix case for windows tests
tmpdir = os.path.normcase(tmpdir)
on_windows = sys.platform == 'win32'
pth_file = os.path.join(tmpdir, 'foo.pth')
relative = '../../example'
if on_windows:
share = '\\\\example\\share\\'
share_com = '\\\\example.com\\share\\'
# Create a .pth file for testing
with open(pth_file, 'w') as f:
f.writelines([tmpdir, '\n',
relative, '\n'])
if on_windows:
f.writelines([share, '\n',
share_com, '\n'])
# Add paths to be removed
pth = UninstallPthEntries(pth_file)
pth.add(tmpdir)
pth.add(relative)
if on_windows:
pth.add(share)
pth.add(share_com)
# Check that the paths were added to entries
if on_windows:
check = set([tmpdir, relative, share, share_com])
else:
check = set([tmpdir, relative])
assert pth.entries == check
@pytest.mark.skipif("sys.platform == 'win32'")
def test_add_symlink(self, tmpdir, monkeypatch):
monkeypatch.setattr(pip._internal.req.req_uninstall, 'is_local',
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册