Skip to content

Commit a8510bc

Browse files
authored
Merge pull request #6638 from cjerdonek/debug-command
Add a "pip debug" command
2 parents ec73d72 + d67acca commit a8510bc

File tree

12 files changed

+251
-37
lines changed

12 files changed

+251
-37
lines changed

news/6638.feature

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add a new command ``pip debug`` that can display e.g. the list of compatible
2+
tags for the current Python.

src/pip/_internal/cli/base_command.py

+1-11
Original file line numberDiff line numberDiff line change
@@ -324,10 +324,7 @@ def _build_package_finder(
324324
self,
325325
options, # type: Values
326326
session, # type: PipSession
327-
platform=None, # type: Optional[str]
328-
py_version_info=None, # type: Optional[Tuple[int, ...]]
329-
abi=None, # type: Optional[str]
330-
implementation=None, # type: Optional[str]
327+
target_python=None, # type: Optional[TargetPython]
331328
ignore_requires_python=None, # type: Optional[bool]
332329
):
333330
# type: (...) -> PackageFinder
@@ -346,13 +343,6 @@ def _build_package_finder(
346343
ignore_requires_python=ignore_requires_python,
347344
)
348345

349-
target_python = TargetPython(
350-
platform=platform,
351-
py_version_info=py_version_info,
352-
abi=abi,
353-
implementation=implementation,
354-
)
355-
356346
return PackageFinder.create(
357347
search_scope=search_scope,
358348
selection_prefs=selection_prefs,

src/pip/_internal/cli/cmdoptions.py

+22
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from pip._internal.models.format_control import FormatControl
2323
from pip._internal.models.index import PyPI
2424
from pip._internal.models.search_scope import SearchScope
25+
from pip._internal.models.target_python import TargetPython
2526
from pip._internal.utils.hashes import STRONG_HASHES
2627
from pip._internal.utils.misc import redact_password_from_url
2728
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@@ -356,6 +357,7 @@ def find_links():
356357

357358

358359
def make_search_scope(options, suppress_no_index=False):
360+
# type: (Values, bool) -> SearchScope
359361
"""
360362
:param suppress_no_index: Whether to ignore the --no-index option
361363
when constructing the SearchScope object.
@@ -600,6 +602,26 @@ def _handle_python_version(option, opt_str, value, parser):
600602
) # type: Callable[..., Option]
601603

602604

605+
def add_target_python_options(cmd_opts):
606+
# type: (OptionGroup) -> None
607+
cmd_opts.add_option(platform())
608+
cmd_opts.add_option(python_version())
609+
cmd_opts.add_option(implementation())
610+
cmd_opts.add_option(abi())
611+
612+
613+
def make_target_python(options):
614+
# type: (Values) -> TargetPython
615+
target_python = TargetPython(
616+
platform=options.platform,
617+
py_version_info=options.python_version,
618+
abi=options.abi,
619+
implementation=options.implementation,
620+
)
621+
622+
return target_python
623+
624+
603625
def prefer_binary():
604626
# type: () -> Option
605627
return Option(

src/pip/_internal/cli/main_parser.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import os
55
import sys
66

7-
from pip import __version__
87
from pip._internal.cli import cmdoptions
98
from pip._internal.cli.parser import (
109
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
@@ -13,7 +12,7 @@
1312
commands_dict, get_similar_commands, get_summaries,
1413
)
1514
from pip._internal.exceptions import CommandError
16-
from pip._internal.utils.misc import get_prog
15+
from pip._internal.utils.misc import get_pip_version, get_prog
1716
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
1817

1918
if MYPY_CHECK_RUNNING:
@@ -39,12 +38,7 @@ def create_main_parser():
3938
parser = ConfigOptionParser(**parser_kw)
4039
parser.disable_interspersed_args()
4140

42-
pip_pkg_dir = os.path.abspath(os.path.join(
43-
os.path.dirname(__file__), "..", "..",
44-
))
45-
parser.version = 'pip %s from %s (python %s)' % (
46-
__version__, pip_pkg_dir, sys.version[:3],
47-
)
41+
parser.version = get_pip_version()
4842

4943
# add the general options
5044
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)

src/pip/_internal/commands/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from pip._internal.commands.completion import CompletionCommand
77
from pip._internal.commands.configuration import ConfigurationCommand
8+
from pip._internal.commands.debug import DebugCommand
89
from pip._internal.commands.download import DownloadCommand
910
from pip._internal.commands.freeze import FreezeCommand
1011
from pip._internal.commands.hash import HashCommand
@@ -36,6 +37,7 @@
3637
WheelCommand,
3738
HashCommand,
3839
CompletionCommand,
40+
DebugCommand,
3941
HelpCommand,
4042
] # type: List[Type[Command]]
4143

src/pip/_internal/commands/debug.py

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from __future__ import absolute_import
2+
3+
import logging
4+
import sys
5+
6+
from pip._internal.cli import cmdoptions
7+
from pip._internal.cli.base_command import Command
8+
from pip._internal.cli.cmdoptions import make_target_python
9+
from pip._internal.cli.status_codes import SUCCESS
10+
from pip._internal.utils.logging import indent_log
11+
from pip._internal.utils.misc import get_pip_version
12+
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
13+
from pip._internal.wheel import format_tag
14+
15+
if MYPY_CHECK_RUNNING:
16+
from typing import Any, List
17+
from optparse import Values
18+
19+
logger = logging.getLogger(__name__)
20+
21+
22+
def show_value(name, value):
23+
# type: (str, str) -> None
24+
logger.info('{}: {}'.format(name, value))
25+
26+
27+
def show_sys_implementation():
28+
# type: () -> None
29+
logger.info('sys.implementation:')
30+
if hasattr(sys, 'implementation'):
31+
implementation = sys.implementation # type: ignore
32+
implementation_name = implementation.name
33+
else:
34+
implementation_name = ''
35+
36+
with indent_log():
37+
show_value('name', implementation_name)
38+
39+
40+
def show_tags(options):
41+
# type: (Values) -> None
42+
tag_limit = 10
43+
44+
target_python = make_target_python(options)
45+
tags = target_python.get_tags()
46+
47+
# Display the target options that were explicitly provided.
48+
formatted_target = target_python.format_given()
49+
suffix = ''
50+
if formatted_target:
51+
suffix = ' (target: {})'.format(formatted_target)
52+
53+
msg = 'Compatible tags: {}{}'.format(len(tags), suffix)
54+
logger.info(msg)
55+
56+
if options.verbose < 1 and len(tags) > tag_limit:
57+
tags_limited = True
58+
tags = tags[:tag_limit]
59+
else:
60+
tags_limited = False
61+
62+
with indent_log():
63+
for tag in tags:
64+
logger.info(format_tag(tag))
65+
66+
if tags_limited:
67+
msg = (
68+
'...\n'
69+
'[First {tag_limit} tags shown. Pass --verbose to show all.]'
70+
).format(tag_limit=tag_limit)
71+
logger.info(msg)
72+
73+
74+
class DebugCommand(Command):
75+
"""
76+
Display debug information.
77+
"""
78+
79+
name = 'debug'
80+
usage = """
81+
%prog <options>"""
82+
summary = 'Show information useful for debugging.'
83+
ignore_require_venv = True
84+
85+
def __init__(self, *args, **kw):
86+
super(DebugCommand, self).__init__(*args, **kw)
87+
88+
cmd_opts = self.cmd_opts
89+
cmdoptions.add_target_python_options(cmd_opts)
90+
self.parser.insert_option_group(0, cmd_opts)
91+
92+
def run(self, options, args):
93+
# type: (Values, List[Any]) -> int
94+
show_value('pip version', get_pip_version())
95+
show_value('sys.version', sys.version)
96+
show_value('sys.executable', sys.executable)
97+
show_value('sys.platform', sys.platform)
98+
show_sys_implementation()
99+
100+
show_tags(options)
101+
102+
return SUCCESS

src/pip/_internal/commands/download.py

+4-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from pip._internal.cli import cmdoptions
77
from pip._internal.cli.base_command import RequirementCommand
8+
from pip._internal.cli.cmdoptions import make_target_python
89
from pip._internal.legacy_resolve import Resolver
910
from pip._internal.operations.prepare import RequirementPreparer
1011
from pip._internal.req import RequirementSet
@@ -69,10 +70,7 @@ def __init__(self, *args, **kw):
6970
help=("Download packages into <dir>."),
7071
)
7172

72-
cmd_opts.add_option(cmdoptions.platform())
73-
cmd_opts.add_option(cmdoptions.python_version())
74-
cmd_opts.add_option(cmdoptions.implementation())
75-
cmd_opts.add_option(cmdoptions.abi())
73+
cmdoptions.add_target_python_options(cmd_opts)
7674

7775
index_opts = cmdoptions.make_option_group(
7876
cmdoptions.index_group,
@@ -96,13 +94,11 @@ def run(self, options, args):
9694
ensure_dir(options.download_dir)
9795

9896
with self._build_session(options) as session:
97+
target_python = make_target_python(options)
9998
finder = self._build_package_finder(
10099
options=options,
101100
session=session,
102-
platform=options.platform,
103-
py_version_info=options.python_version,
104-
abi=options.abi,
105-
implementation=options.implementation,
101+
target_python=target_python,
106102
)
107103
build_delete = (not (options.no_clean or options.build_dir))
108104
if options.cache_dir and not check_path_owner(options.cache_dir):

src/pip/_internal/commands/install.py

+4-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from pip._internal.cache import WheelCache
1313
from pip._internal.cli import cmdoptions
1414
from pip._internal.cli.base_command import RequirementCommand
15+
from pip._internal.cli.cmdoptions import make_target_python
1516
from pip._internal.cli.status_codes import ERROR
1617
from pip._internal.exceptions import (
1718
CommandError, InstallationError, PreviousBuildDirError,
@@ -114,10 +115,7 @@ def __init__(self, *args, **kw):
114115
'<dir>. Use --upgrade to replace existing packages in <dir> '
115116
'with new versions.'
116117
)
117-
cmd_opts.add_option(cmdoptions.platform())
118-
cmd_opts.add_option(cmdoptions.python_version())
119-
cmd_opts.add_option(cmdoptions.implementation())
120-
cmd_opts.add_option(cmdoptions.abi())
118+
cmdoptions.add_target_python_options(cmd_opts)
121119

122120
cmd_opts.add_option(
123121
'--user',
@@ -285,13 +283,11 @@ def run(self, options, args):
285283
global_options = options.global_options or []
286284

287285
with self._build_session(options) as session:
286+
target_python = make_target_python(options)
288287
finder = self._build_package_finder(
289288
options=options,
290289
session=session,
291-
platform=options.platform,
292-
py_version_info=options.python_version,
293-
abi=options.abi,
294-
implementation=options.implementation,
290+
target_python=target_python,
295291
ignore_requires_python=options.ignore_requires_python,
296292
)
297293
build_delete = (not (options.no_clean or options.build_dir))

src/pip/_internal/models/target_python.py

+26-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
66

77
if MYPY_CHECK_RUNNING:
8-
from typing import Optional, Tuple
8+
from typing import List, Optional, Tuple
9+
from pip._internal.pep425tags import Pep425Tag
910

1011

1112
class TargetPython(object):
@@ -54,9 +55,32 @@ def __init__(
5455
self.py_version_info = py_version_info
5556

5657
# This is used to cache the return value of get_tags().
57-
self._valid_tags = None
58+
self._valid_tags = None # type: Optional[List[Pep425Tag]]
59+
60+
def format_given(self):
61+
# type: () -> str
62+
"""
63+
Format the given, non-None attributes for display.
64+
"""
65+
display_version = None
66+
if self._given_py_version_info is not None:
67+
display_version = '.'.join(
68+
str(part) for part in self._given_py_version_info
69+
)
70+
71+
key_values = [
72+
('platform', self.platform),
73+
('version_info', display_version),
74+
('abi', self.abi),
75+
('implementation', self.implementation),
76+
]
77+
return ' '.join(
78+
'{}={!r}'.format(key, value) for key, value in key_values
79+
if value is not None
80+
)
5881

5982
def get_tags(self):
83+
# type: () -> List[Pep425Tag]
6084
"""
6185
Return the supported tags to check wheel candidates against.
6286
"""

src/pip/_internal/utils/misc.py

+13
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from pip._vendor.six.moves.urllib import request as urllib_request
2929
from pip._vendor.six.moves.urllib.parse import unquote as urllib_unquote
3030

31+
from pip import __version__
3132
from pip._internal.exceptions import CommandError, InstallationError
3233
from pip._internal.locations import (
3334
running_under_virtualenv, site_packages, user_site, virtualenv_no_global,
@@ -104,6 +105,18 @@ def cast(typ, val):
104105
logger.debug('lzma module is not available')
105106

106107

108+
def get_pip_version():
109+
# type: () -> str
110+
pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..")
111+
pip_pkg_dir = os.path.abspath(pip_pkg_dir)
112+
113+
return (
114+
'pip {} from {} (python {})'.format(
115+
__version__, pip_pkg_dir, sys.version[:3],
116+
)
117+
)
118+
119+
107120
def normalize_version_info(py_version_info):
108121
# type: (Optional[Tuple[int, ...]]) -> Optional[Tuple[int, int, int]]
109122
"""

0 commit comments

Comments
 (0)