mirror of https://github.com/pypa/pip
Merge pull request #6638 from cjerdonek/debug-command
Add a "pip debug" command
This commit is contained in:
commit
a8510bc5e6
|
@ -0,0 +1,2 @@
|
|||
Add a new command ``pip debug`` that can display e.g. the list of compatible
|
||||
tags for the current Python.
|
|
@ -324,10 +324,7 @@ class RequirementCommand(Command):
|
|||
self,
|
||||
options, # type: Values
|
||||
session, # type: PipSession
|
||||
platform=None, # type: Optional[str]
|
||||
py_version_info=None, # type: Optional[Tuple[int, ...]]
|
||||
abi=None, # type: Optional[str]
|
||||
implementation=None, # type: Optional[str]
|
||||
target_python=None, # type: Optional[TargetPython]
|
||||
ignore_requires_python=None, # type: Optional[bool]
|
||||
):
|
||||
# type: (...) -> PackageFinder
|
||||
|
@ -346,13 +343,6 @@ class RequirementCommand(Command):
|
|||
ignore_requires_python=ignore_requires_python,
|
||||
)
|
||||
|
||||
target_python = TargetPython(
|
||||
platform=platform,
|
||||
py_version_info=py_version_info,
|
||||
abi=abi,
|
||||
implementation=implementation,
|
||||
)
|
||||
|
||||
return PackageFinder.create(
|
||||
search_scope=search_scope,
|
||||
selection_prefs=selection_prefs,
|
||||
|
|
|
@ -22,6 +22,7 @@ from pip._internal.locations import USER_CACHE_DIR, src_prefix
|
|||
from pip._internal.models.format_control import FormatControl
|
||||
from pip._internal.models.index import PyPI
|
||||
from pip._internal.models.search_scope import SearchScope
|
||||
from pip._internal.models.target_python import TargetPython
|
||||
from pip._internal.utils.hashes import STRONG_HASHES
|
||||
from pip._internal.utils.misc import redact_password_from_url
|
||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
@ -356,6 +357,7 @@ def find_links():
|
|||
|
||||
|
||||
def make_search_scope(options, suppress_no_index=False):
|
||||
# type: (Values, bool) -> SearchScope
|
||||
"""
|
||||
:param suppress_no_index: Whether to ignore the --no-index option
|
||||
when constructing the SearchScope object.
|
||||
|
@ -600,6 +602,26 @@ abi = partial(
|
|||
) # type: Callable[..., Option]
|
||||
|
||||
|
||||
def add_target_python_options(cmd_opts):
|
||||
# type: (OptionGroup) -> None
|
||||
cmd_opts.add_option(platform())
|
||||
cmd_opts.add_option(python_version())
|
||||
cmd_opts.add_option(implementation())
|
||||
cmd_opts.add_option(abi())
|
||||
|
||||
|
||||
def make_target_python(options):
|
||||
# type: (Values) -> TargetPython
|
||||
target_python = TargetPython(
|
||||
platform=options.platform,
|
||||
py_version_info=options.python_version,
|
||||
abi=options.abi,
|
||||
implementation=options.implementation,
|
||||
)
|
||||
|
||||
return target_python
|
||||
|
||||
|
||||
def prefer_binary():
|
||||
# type: () -> Option
|
||||
return Option(
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from pip import __version__
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.parser import (
|
||||
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
|
||||
|
@ -13,7 +12,7 @@ from pip._internal.commands import (
|
|||
commands_dict, get_similar_commands, get_summaries,
|
||||
)
|
||||
from pip._internal.exceptions import CommandError
|
||||
from pip._internal.utils.misc import get_prog
|
||||
from pip._internal.utils.misc import get_pip_version, get_prog
|
||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
|
@ -39,12 +38,7 @@ def create_main_parser():
|
|||
parser = ConfigOptionParser(**parser_kw)
|
||||
parser.disable_interspersed_args()
|
||||
|
||||
pip_pkg_dir = os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__), "..", "..",
|
||||
))
|
||||
parser.version = 'pip %s from %s (python %s)' % (
|
||||
__version__, pip_pkg_dir, sys.version[:3],
|
||||
)
|
||||
parser.version = get_pip_version()
|
||||
|
||||
# add the general options
|
||||
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
|
||||
|
|
|
@ -5,6 +5,7 @@ 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
|
||||
|
@ -36,6 +37,7 @@ commands_order = [
|
|||
WheelCommand,
|
||||
HashCommand,
|
||||
CompletionCommand,
|
||||
DebugCommand,
|
||||
HelpCommand,
|
||||
] # type: List[Type[Command]]
|
||||
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.base_command import Command
|
||||
from pip._internal.cli.cmdoptions import make_target_python
|
||||
from pip._internal.cli.status_codes import SUCCESS
|
||||
from pip._internal.utils.logging import indent_log
|
||||
from pip._internal.utils.misc import get_pip_version
|
||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
from pip._internal.wheel import format_tag
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any, List
|
||||
from optparse import Values
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def show_value(name, value):
|
||||
# type: (str, str) -> None
|
||||
logger.info('{}: {}'.format(name, value))
|
||||
|
||||
|
||||
def show_sys_implementation():
|
||||
# type: () -> None
|
||||
logger.info('sys.implementation:')
|
||||
if hasattr(sys, 'implementation'):
|
||||
implementation = sys.implementation # type: ignore
|
||||
implementation_name = implementation.name
|
||||
else:
|
||||
implementation_name = ''
|
||||
|
||||
with indent_log():
|
||||
show_value('name', implementation_name)
|
||||
|
||||
|
||||
def show_tags(options):
|
||||
# type: (Values) -> None
|
||||
tag_limit = 10
|
||||
|
||||
target_python = make_target_python(options)
|
||||
tags = target_python.get_tags()
|
||||
|
||||
# Display the target options that were explicitly provided.
|
||||
formatted_target = target_python.format_given()
|
||||
suffix = ''
|
||||
if formatted_target:
|
||||
suffix = ' (target: {})'.format(formatted_target)
|
||||
|
||||
msg = 'Compatible tags: {}{}'.format(len(tags), suffix)
|
||||
logger.info(msg)
|
||||
|
||||
if options.verbose < 1 and len(tags) > tag_limit:
|
||||
tags_limited = True
|
||||
tags = tags[:tag_limit]
|
||||
else:
|
||||
tags_limited = False
|
||||
|
||||
with indent_log():
|
||||
for tag in tags:
|
||||
logger.info(format_tag(tag))
|
||||
|
||||
if tags_limited:
|
||||
msg = (
|
||||
'...\n'
|
||||
'[First {tag_limit} tags shown. Pass --verbose to show all.]'
|
||||
).format(tag_limit=tag_limit)
|
||||
logger.info(msg)
|
||||
|
||||
|
||||
class DebugCommand(Command):
|
||||
"""
|
||||
Display debug information.
|
||||
"""
|
||||
|
||||
name = 'debug'
|
||||
usage = """
|
||||
%prog <options>"""
|
||||
summary = 'Show information useful for debugging.'
|
||||
ignore_require_venv = True
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(DebugCommand, self).__init__(*args, **kw)
|
||||
|
||||
cmd_opts = self.cmd_opts
|
||||
cmdoptions.add_target_python_options(cmd_opts)
|
||||
self.parser.insert_option_group(0, cmd_opts)
|
||||
|
||||
def run(self, options, args):
|
||||
# type: (Values, List[Any]) -> int
|
||||
show_value('pip version', get_pip_version())
|
||||
show_value('sys.version', sys.version)
|
||||
show_value('sys.executable', sys.executable)
|
||||
show_value('sys.platform', sys.platform)
|
||||
show_sys_implementation()
|
||||
|
||||
show_tags(options)
|
||||
|
||||
return SUCCESS
|
|
@ -5,6 +5,7 @@ import os
|
|||
|
||||
from pip._internal.cli import cmdoptions
|
||||
from pip._internal.cli.base_command import RequirementCommand
|
||||
from pip._internal.cli.cmdoptions import make_target_python
|
||||
from pip._internal.legacy_resolve import Resolver
|
||||
from pip._internal.operations.prepare import RequirementPreparer
|
||||
from pip._internal.req import RequirementSet
|
||||
|
@ -69,10 +70,7 @@ class DownloadCommand(RequirementCommand):
|
|||
help=("Download packages into <dir>."),
|
||||
)
|
||||
|
||||
cmd_opts.add_option(cmdoptions.platform())
|
||||
cmd_opts.add_option(cmdoptions.python_version())
|
||||
cmd_opts.add_option(cmdoptions.implementation())
|
||||
cmd_opts.add_option(cmdoptions.abi())
|
||||
cmdoptions.add_target_python_options(cmd_opts)
|
||||
|
||||
index_opts = cmdoptions.make_option_group(
|
||||
cmdoptions.index_group,
|
||||
|
@ -96,13 +94,11 @@ class DownloadCommand(RequirementCommand):
|
|||
ensure_dir(options.download_dir)
|
||||
|
||||
with self._build_session(options) as session:
|
||||
target_python = make_target_python(options)
|
||||
finder = self._build_package_finder(
|
||||
options=options,
|
||||
session=session,
|
||||
platform=options.platform,
|
||||
py_version_info=options.python_version,
|
||||
abi=options.abi,
|
||||
implementation=options.implementation,
|
||||
target_python=target_python,
|
||||
)
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
if options.cache_dir and not check_path_owner(options.cache_dir):
|
||||
|
|
|
@ -12,6 +12,7 @@ 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.status_codes import ERROR
|
||||
from pip._internal.exceptions import (
|
||||
CommandError, InstallationError, PreviousBuildDirError,
|
||||
|
@ -114,10 +115,7 @@ class InstallCommand(RequirementCommand):
|
|||
'<dir>. Use --upgrade to replace existing packages in <dir> '
|
||||
'with new versions.'
|
||||
)
|
||||
cmd_opts.add_option(cmdoptions.platform())
|
||||
cmd_opts.add_option(cmdoptions.python_version())
|
||||
cmd_opts.add_option(cmdoptions.implementation())
|
||||
cmd_opts.add_option(cmdoptions.abi())
|
||||
cmdoptions.add_target_python_options(cmd_opts)
|
||||
|
||||
cmd_opts.add_option(
|
||||
'--user',
|
||||
|
@ -285,13 +283,11 @@ class InstallCommand(RequirementCommand):
|
|||
global_options = options.global_options or []
|
||||
|
||||
with self._build_session(options) as session:
|
||||
target_python = make_target_python(options)
|
||||
finder = self._build_package_finder(
|
||||
options=options,
|
||||
session=session,
|
||||
platform=options.platform,
|
||||
py_version_info=options.python_version,
|
||||
abi=options.abi,
|
||||
implementation=options.implementation,
|
||||
target_python=target_python,
|
||||
ignore_requires_python=options.ignore_requires_python,
|
||||
)
|
||||
build_delete = (not (options.no_clean or options.build_dir))
|
||||
|
|
|
@ -5,7 +5,8 @@ from pip._internal.utils.misc import normalize_version_info
|
|||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Optional, Tuple
|
||||
from typing import List, Optional, Tuple
|
||||
from pip._internal.pep425tags import Pep425Tag
|
||||
|
||||
|
||||
class TargetPython(object):
|
||||
|
@ -54,9 +55,32 @@ class TargetPython(object):
|
|||
self.py_version_info = py_version_info
|
||||
|
||||
# This is used to cache the return value of get_tags().
|
||||
self._valid_tags = None
|
||||
self._valid_tags = None # type: Optional[List[Pep425Tag]]
|
||||
|
||||
def format_given(self):
|
||||
# type: () -> str
|
||||
"""
|
||||
Format the given, non-None attributes for display.
|
||||
"""
|
||||
display_version = None
|
||||
if self._given_py_version_info is not None:
|
||||
display_version = '.'.join(
|
||||
str(part) for part in self._given_py_version_info
|
||||
)
|
||||
|
||||
key_values = [
|
||||
('platform', self.platform),
|
||||
('version_info', display_version),
|
||||
('abi', self.abi),
|
||||
('implementation', self.implementation),
|
||||
]
|
||||
return ' '.join(
|
||||
'{}={!r}'.format(key, value) for key, value in key_values
|
||||
if value is not None
|
||||
)
|
||||
|
||||
def get_tags(self):
|
||||
# type: () -> List[Pep425Tag]
|
||||
"""
|
||||
Return the supported tags to check wheel candidates against.
|
||||
"""
|
||||
|
|
|
@ -28,6 +28,7 @@ from pip._vendor.six.moves.urllib import parse as urllib_parse
|
|||
from pip._vendor.six.moves.urllib import request as urllib_request
|
||||
from pip._vendor.six.moves.urllib.parse import unquote as urllib_unquote
|
||||
|
||||
from pip import __version__
|
||||
from pip._internal.exceptions import CommandError, InstallationError
|
||||
from pip._internal.locations import (
|
||||
running_under_virtualenv, site_packages, user_site, virtualenv_no_global,
|
||||
|
@ -104,6 +105,18 @@ except ImportError:
|
|||
logger.debug('lzma module is not available')
|
||||
|
||||
|
||||
def get_pip_version():
|
||||
# type: () -> str
|
||||
pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..")
|
||||
pip_pkg_dir = os.path.abspath(pip_pkg_dir)
|
||||
|
||||
return (
|
||||
'pip {} from {} (python {})'.format(
|
||||
__version__, pip_pkg_dir, sys.version[:3],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def normalize_version_info(py_version_info):
|
||||
# type: (Optional[Tuple[int, ...]]) -> Optional[Tuple[int, int, int]]
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import pytest
|
||||
|
||||
from pip._internal import pep425tags
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'args',
|
||||
[
|
||||
[],
|
||||
['--verbose'],
|
||||
]
|
||||
)
|
||||
def test_debug(script, args):
|
||||
"""
|
||||
Check simple option cases.
|
||||
"""
|
||||
args = ['debug'] + args
|
||||
result = script.pip(*args)
|
||||
stdout = result.stdout
|
||||
|
||||
assert 'sys.executable: ' in stdout
|
||||
assert 'sys.platform: ' in stdout
|
||||
assert 'sys.implementation:' in stdout
|
||||
|
||||
tags = pep425tags.get_supported()
|
||||
expected_tag_header = 'Compatible tags: {}'.format(len(tags))
|
||||
assert expected_tag_header in stdout
|
||||
|
||||
show_verbose_note = '--verbose' not in args
|
||||
assert (
|
||||
'...\n [First 10 tags shown. Pass --verbose to show all.]' in stdout
|
||||
) == show_verbose_note
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'args, expected',
|
||||
[
|
||||
(['--python-version', '3.7'], "(target: version_info='3.7')"),
|
||||
]
|
||||
)
|
||||
def test_debug__target_options(script, args, expected):
|
||||
"""
|
||||
Check passing target-related options.
|
||||
"""
|
||||
args = ['debug'] + args
|
||||
result = script.pip(*args)
|
||||
stdout = result.stdout
|
||||
|
||||
assert 'Compatible tags: ' in stdout
|
||||
assert expected in stdout
|
|
@ -47,6 +47,29 @@ class TestTargetPython:
|
|||
assert target_python.py_version_info == CURRENT_PY_VERSION_INFO
|
||||
assert target_python.py_version == current_major_minor
|
||||
|
||||
@pytest.mark.parametrize('kwargs, expected', [
|
||||
({}, ''),
|
||||
(dict(py_version_info=(3, 6)), "version_info='3.6'"),
|
||||
(
|
||||
dict(platform='darwin', py_version_info=(3, 6)),
|
||||
"platform='darwin' version_info='3.6'",
|
||||
),
|
||||
(
|
||||
dict(
|
||||
platform='darwin', py_version_info=(3, 6), abi='cp36m',
|
||||
implementation='cp'
|
||||
),
|
||||
(
|
||||
"platform='darwin' version_info='3.6' abi='cp36m' "
|
||||
"implementation='cp'"
|
||||
),
|
||||
),
|
||||
])
|
||||
def test_format_given(self, kwargs, expected):
|
||||
target_python = TargetPython(**kwargs)
|
||||
actual = target_python.format_given()
|
||||
assert actual == expected
|
||||
|
||||
@pytest.mark.parametrize('py_version_info, expected_versions', [
|
||||
((), ['']),
|
||||
((2, ), ['2']),
|
||||
|
|
Loading…
Reference in New Issue