Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a "pip debug" command #6638

Merged
merged 4 commits into from
Jul 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions news/6638.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add a new command ``pip debug`` that can display e.g. the list of compatible
tags for the current Python.
12 changes: 1 addition & 11 deletions src/pip/_internal/cli/base_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,7 @@ def _build_package_finder(
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
Expand All @@ -338,13 +335,6 @@ def _build_package_finder(
"""
search_scope = make_search_scope(options)

target_python = TargetPython(
platform=platform,
py_version_info=py_version_info,
abi=abi,
implementation=implementation,
)

return PackageFinder.create(
search_scope=search_scope,
format_control=options.format_control,
Expand Down
22 changes: 22 additions & 0 deletions src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -600,6 +602,26 @@ def _handle_python_version(option, opt_str, value, parser):
) # 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(
Expand Down
10 changes: 2 additions & 8 deletions src/pip/_internal/cli/main_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -13,7 +12,7 @@
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:
Expand All @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/pip/_internal/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

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
Expand Down Expand Up @@ -36,6 +37,7 @@
WheelCommand,
HashCommand,
CompletionCommand,
DebugCommand,
HelpCommand,
] # type: List[Type[Command]]

Expand Down
102 changes: 102 additions & 0 deletions src/pip/_internal/commands/debug.py
Original file line number Diff line number Diff line change
@@ -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
12 changes: 4 additions & 8 deletions src/pip/_internal/commands/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

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
Expand Down Expand Up @@ -69,10 +70,7 @@ def __init__(self, *args, **kw):
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,
Expand All @@ -96,13 +94,11 @@ def run(self, options, args):
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):
Expand Down
12 changes: 4 additions & 8 deletions src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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,
Expand Down Expand Up @@ -114,10 +115,7 @@ def __init__(self, *args, **kw):
'<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',
Expand Down Expand Up @@ -285,13 +283,11 @@ def run(self, options, args):
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))
Expand Down
28 changes: 26 additions & 2 deletions src/pip/_internal/models/target_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
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):
Expand Down Expand Up @@ -54,9 +55,32 @@ def __init__(
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.
"""
Expand Down
13 changes: 13 additions & 0 deletions src/pip/_internal/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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,
Expand Down Expand Up @@ -104,6 +105,18 @@ def cast(typ, val):
logger.debug('lzma module is not available')


def get_pip_version():
cjerdonek marked this conversation as resolved.
Show resolved Hide resolved
# 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]]
"""
Expand Down
Loading